Skip to content

fix(sandbox): count only grpc-js's own statuses as gateway outages - #4066

Merged
freeqaz merged 3 commits into
mainfrom
fix-outage-detect-1029
Oct 29, 2026
Merged

freeqaz merged 3 commits into
mainfrom
fix-outage-detect-1029

Conversation

@freeqaz

@freeqaz freeqaz commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

The OpenShell client decides whether a failed gateway request is a Sandbox gateway outage (SANDBOX_GATEWAY_UNAVAILABLE: a revision pass waits for it instead of spending its attempt budget). The decision matched the gRPC details text only:

  • A status the gateway sends itself with the same text as grpc-js's own (Connection dropped, read ECONNRESET, Stream refused by server, Name resolution failed ..., No connection established. Last error: connect ECONNREFUSED ...) counted as an outage. A gateway interceptor relays the reason and status it chooses, so a policy refusal worded like that deferred revision passes until the deployment deadline.
  • A call that still had no connection when its deadline passed (a connect that gets no reply: a lost node, a stale Service address) came back as DEADLINE_EXCEEDED ... Waiting for LB pick and spent the attempt budget. The same blackhole with a longer deadline (DeleteSandbox, 120 s) showed up as connect ETIMEDOUT and counted as an outage.
  • The wire tests had no read ECONNRESET or unresolvable-name case, so dropping those patterns, the ^...$ anchor or the status-code check went unnoticed.

Change

  • gatewayDidNotAnswer also requires that the status has no metadata. grpc-js gives a status it makes up empty metadata. A status the gateway sends before any response message is a trailers-only response, and grpc-js keeps its other headers (content-type, date) as metadata. tonic sends interceptor refusals and handler errors this way.
  • The allowlist is one table of (gRPC code, details pattern). It adds DEADLINE_EXCEEDED with grpc-js's exact Deadline exceeded after <n>s[,<phase>: <n>s]*,Waiting for LB pick text. That text means the request was never sent. OpenShellGatewayUnavailableError now keeps the real gRPC status (4 or 14), so the protected-Git expiry check still reports such a request as timeout.
  • Removed the No addresses resolved alternative: grpc-js's DNS lookup rejects a name that has no addresses ("Name resolution failed"), and an IP endpoint always has one address, so this client can never get that text.
  • No change for No connection established. Last error: Failed to connect (<ts>). A plaintext client at a TLS-only port gets exactly the same details as a stale port-forward, so the request stays on the attempt budget. Envoy's no healthy upstream / empty-message UNAVAILABLE is also unchanged: a proxy sends it, and the controller connects to the gateway Service directly.
  • Retry budgets, the Sandbox Driver contract and the credential paths do not change. TLS and client-certificate refusals (... alert certificate required) are not on the allowlist, so they stay ordinary failures.

Verification

  • tests/conformance/openshell-gateway-wire.test.mjs, outage test, new wire cases:
    • The gateway answers each of grpc-js's own wordings (UNAVAILABLE and DEADLINE_EXCEEDED): ordinary. These fail on main.
    • A TCP reset under an in-flight call (read ECONNRESET): outage.
    • An unresolvable .invalid Service name: outage.
    • A listener that accepts and stays silent until the deadline (DEADLINE_EXCEEDED, grpcStatus 4): outage. This fails on main.
    • Statuses sent in trailers after response headers (Connection dropped by gateway policy, policy: Connection dropped, INTERNAL Connection dropped): ordinary.
    • A gateway that accepts the request stream and never answers (DEADLINE_EXCEEDED naming remote_addr, so the request was sent): ordinary, grpcStatus 4.
  • OpenShell mTLS ... refuses missing or foreign identities: on the IP channel, each refused client identity is now asserted to be an ordinary failure, never a Sandbox gateway outage. At localhost (which can also resolve to ::1, where nothing listens in the test) grpc-js reports only the last address's error, which can be the refused ::1 connect; that limit predates this change and is tracked separately.
  • Internal review added mutants: any DEADLINE_EXCEEDED counted as an outage, any No connection established text counted as an outage, and any TLS/alert wording counted as an outage. All three were killed; the first only with the new never-answered case, and the TLS one now also by the refused client identities.
  • Mutation run at 2 CPUs: all 14 mutants were killed. The mutants removed each pattern (Connection dropped, read/write reset, Stream refused, Name resolution failed, ECONNREFUSED), removed either anchor, removed the status-code check, removed the metadata check, removed the deadline pattern, set the deadline outage's status to 14, never classed ExecSandbox as an outage, restored the fix(controller): wait out a sandbox gateway outage without spending the attempt budget #2957 "any UNAVAILABLE" rule, or used main's source.
  • On a remote test VM, against this branch rebased on current main: the whole wire test file passed 20/20 at 2 CPUs in a private network namespace. Typecheck (tsc --build tsconfig.json tests/tsconfig.json) passed. eslint and prettier are clean on the touched files.

Risks

  • A status sent in trailers after response headers and a body has no metadata, so only its code and exact details separate it from grpc-js's own. At the pinned OpenShell this happens only for an error in the middle of an ExecSandbox stream, and the text would have to match one of grpc-js's wordings.
  • A connect that gets no reply until the request deadline now waits until the deployment deadline instead of failing within the attempt budget. For example, a NetworkPolicy on a CNI that drops packets blocks the controller's egress to the gateway. This is the same as the ECONNREFUSED that such a policy already causes on CNIs that reject, and as connect ETIMEDOUT with longer deadlines. The deployment still fails, with SANDBOX_GATEWAY_UNAVAILABLE.
  • The deadline pattern follows grpc-js 1.14's text. If a later grpc-js changes the wording, the case becomes an ordinary failure again (the fail-safe direction), and the wire test fails.

A gateway interceptor can answer with grpc-js's own wording (for example
UNAVAILABLE 'Connection dropped'); that answer carries the response's
content-type and date as status metadata, while a status grpc-js makes up
has none. Require empty metadata before matching the details.

A call that still had no connection at its deadline (DEADLINE_EXCEEDED
'Waiting for LB pick') was never sent, so it is an outage too; the outage
error now keeps the actual gRPC status. Drop the unreachable 'No addresses
resolved' alternative. 'Failed to connect' stays ordinary: a plaintext
client at a TLS port reads the same.

Wire cases for read ECONNRESET, an unresolvable name, a silent listener,
server-sent lookalikes and post-header trailers kill the pattern, anchor,
status and metadata mutants (findings 2048-2050).
@freeqaz
freeqaz requested a review from a team as a code owner October 29, 2026 16:20
@clawsweeper

clawsweeper Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

@clawsweeper clawsweeper Bot added clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-repro Preliminary submission classification; full source review pending. P2 Normal priority bug or improvement with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-repro Preliminary submission classification; full source review pending. labels Oct 29, 2026
@clawsweeper

clawsweeper Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

What this changes

This PR distinguishes gateway replies from client-generated transport failures and treats an unsent request deadline as a gateway outage.

Example: The gateway replies with UNAVAILABLE and the exact details “Connection dropped”.

  • Before: Revision work mistakes the reply for an outage and waits until the convergence deadline.
  • After: Response metadata identifies the gateway reply, so the failure consumes the normal retry-attempt budget.

Review scores

Measure Result What it means
Overall readiness 🦐 gold shrimp (3/6) Useful, focused repair with credible real-transport coverage and no actionable findings; deployed workflow evidence remains limited.
Proof confidence 🦐 gold shrimp (3/6) This collaborator-authored PR is outside the ordinary contributor proof gate; supplied verification exercises the production client through local transport harnesses, with no shipped-controller artifact supplied.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Product

Kind: Bug fix · Worth it: Yes · Fix scope: Complete
User problem: Deployments can wait out a gateway refusal as though it were an outage, or exhaust retries while no gateway connection was established.
Reason: The collaborator-authored direction restores the existing distinction between unanswered requests and gateway replies without adding configuration or platform capabilities.

Merge readiness

✅ Ready for maintainer review

Keep this PR open: it repairs source-confirmed gateway failure classification on main, and no actionable introduced defect was found.

Priority: P2
Reviewed head: e97e0ca25c5b4fbc3b811057bf9e8304ca03716d

Before merge

None.

Findings

None.

Tests

  • Missing end-to-end proof: No supplied artifact demonstrates the shipped revision worker handling these exact server-reply and unsent-deadline scenarios.
Agent review details

How this fits together

The controller's OpenShell client converts gRPC failures into dependency errors; the revision worker uses their class to choose budgeted retries or deadline-bound waiting.

flowchart TB
  Work[Revision work] --> Worker[Controller worker]
  Worker --> Compute[Compute Driver]
  Compute --> Client[OpenShell client]
  Client --> Gateway[OpenShell gateway]
  Gateway --> Classification[Failure classification]
  Classification --> Worker
Loading

Technical review

Best possible solution:

Land the focused classifier repair in the existing OpenShell client while preserving the worker's established retry policies.

Do we have a high-confidence way to reproduce the issue?

Current-main classification and the pinned grpc-js implementation establish both failure paths; the PR reports base-fail/head-pass wire cases, but this review executed no target code.

Is this the best way to solve the issue?

The repair uses the existing classifier and error owner, preserves sanitized diagnostics and status consumers, and adds meaningful transport coverage without changing Driver contracts or retry budgets.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning medium; reviewed against 24862992720b.

Provenance checked

  • Gateway reply classification keeps the original intent (18e2a0e: Separate unanswered requests from gateway refusals and TLS failures so deterministic failures retain budgeted retries.)
  • Unsent DEADLINE_EXCEEDED classification changes intended behavior with a stated reason (3d5cb38: Deadline errors remained ordinary because a slow call does not establish an outage; this PR narrows the exception to grpc-js's indication that no connection was selected.)

Testing

Proof path: in-process harness.

Security

None.

Evidence

What I checked:

Likely related people:

  • Steven Lee: Raw commit 4927a6a adds apps/controller/src/drivers/sandbox/openshell-gateway-errors.ts:2 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: medium; commits: 4927a6a77eb3; files: apps/controller/src/drivers/sandbox/openshell-gateway-errors.ts)

Review metrics

Metric Value Why it matters
Introduced production and test changes Production +61/-22; tests +117/-10 Most added lines exercise distinct transport failures and server replies through the existing wire test.

Labels

Label changes:

  • add P2: Misclassification delays deterministic gateway failures or prematurely exhausts recoverable deployment attempts, without evidence of an urgent production incident.
  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR.

Label justifications:

  • P2: Misclassification delays deterministic gateway failures or prematurely exhausts recoverable deployment attempts, without evidence of an urgent production incident.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR.

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Optionally attach redacted controller output showing the changed retry outcome for a gateway reply and an unsent request deadline.

Rating scale

6/6 🦀 challenger crab · 5/6 🦞 diamond lobster · 4/6 🐚 platinum hermit · 3/6 🦐 gold shrimp · 2/6 🦪 silver shellfish · 1/6 🧂 unranked krab. Overall follows the weaker of proof and patch quality; ✨ marks media proof (a screenshot, video, or linked artifact) that directly shows the changed behavior.

Workflow

ClawSweeper edits this one comment on every review. Comment @clawsweeper re-review for a fresh review only. Repair and merge execution lanes are retired.

Reviewed October 29, 2026, 12:33 PM ET / 16:33 UTC.

…ordinary failures

A request the gateway received but never answered times out with grpc-js's
remote_addr deadline text, not "Waiting for LB pick"; it must stay on the
attempt budget. A refused mTLS identity must never be a Sandbox gateway
outage. Both cases were only implied: a matcher that took any
DEADLINE_EXCEEDED, or any TLS/alert wording, survived the wire tests.
CI resolves localhost to 127.0.0.1 and then ::1. Nothing listens on ::1 in
this test, and grpc-js reports only the last address's error, so a refused
mTLS identity at localhost can surface as "connect ECONNREFUSED ::1:<port>"
and read as an unanswered request. The IP channel has one address, so it
still proves that a refused identity is an ordinary failure.
@freeqaz

freeqaz commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
Exact review queued.

Re-review progress:

@freeqaz
freeqaz merged commit cda533b into main Oct 29, 2026
41 checks passed
@freeqaz
freeqaz deleted the fix-outage-detect-1029 branch October 29, 2026 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant