You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(execd): defer SSE response headers until first event fires (#912)
* test(e2e): harden flaky cross-SDK sandbox tests
Recurring failures in `real-e2e.yml` traced to three races:
1. Go `TestE2E_FullLifecycle` pings execd via the server proxy the moment
sandbox state flips to Running; execd's TCP listener can accept before
routes register, surfacing as `read: connection reset by peer` on
`/proxy/44772/ping`. Wrap Ping + the first SSE RunCommand in
`require.Eventually` retries.
2. Go `TestE2E_PauseResume` and `TestFilesystem_SetPermissions` hit
`opensandbox: empty sse stream` immediately after Resume / readiness.
Add `runCommandWithRetry` helper in base_e2e_test.go and use it in the
affected call sites.
3. Java/C# NetworkPolicy tests asserted egress blocking after a fixed
`Thread.sleep`/`Task.Delay`; the sidecar accepts the sandbox before
iptables/proxy rules apply, so curl occasionally succeeds and
`assertNotNull(error)` fails. Replace fixed sleeps with
`waitUntilEgressBlocks` / `WaitUntilEgressBlocksAsync` helpers that
poll curl until the policy actually blocks (or fail with the last
observation).
Also retry transient single-line stdout drops on the workingDirectory
`pwd` checks (JS, Java) and the C# env-injection baseline — same SSE
blank-line / first-event race that has bitten multiple SDKs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(execd): defer SSE response headers until first event fires
`RunCommand`, `RunCode`, and `RunInSession` previously committed the
response as `text/event-stream` (via `setupSSEResponse`) before invoking
the runtime. If the runtime returned a synchronous error — for example
because `stdLogDescriptor` could not (re)create `/tmp` after a sandbox
restart, `pathutil.ExpandPathWithEnv` failed to resolve a working
directory under transient env conditions, or `buildCredential` raced
with a uid/gid lookup — the handler then called `RespondError`, which
set Content-Type to `application/json` and wrote a JSON body on top of
the already-committed event-stream response. Clients saw HTTP 200 with
`text/event-stream` and a JSON body that no SSE parser could decode,
producing zero events; the Go SDK reported `opensandbox: empty sse
stream` and JS/Java/C# SDKs surfaced the same race as missing init
events or a vanished single-line stdout (e.g. `pwd` with
`workingDirectory: "/tmp"` returning empty `stdout[0]`).
Make `setupSSEResponse` idempotent (guarded by `sync.Once`) and call it
lazily from `writeSingleEvent`, so headers commit only once an event is
actually being written. Drop the eager `setupSSEResponse` calls in the
three streaming endpoints. Pre-execution synchronous errors now flow
through `RespondError` cleanly with `application/json`, and successful
runs still emit `text/event-stream` on the first event.
Add three regression tests:
- `TestRunCodeSyncErrorEmitsJSONNotSSE`
- `TestRunInSessionSyncErrorEmitsJSONNotSSE`
- `TestRunCodeSuccessStillEmitsSSE`
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test(e2e): drop SDK-side retries now that execd SSE bug is fixed
The execd "empty sse stream" / dropped-event race is fixed at the source
in the previous commit (lazy SSE headers so synchronous runtime errors
return JSON, not a half-formed event-stream). Real SDK clients do not
retry these failures, so the e2e suite shouldn't either — the retries
were masking the bug, not exercising production behaviour.
Revert the SDK-side workarounds:
- JS `pwd workingDirectory:/tmp` retry loop
- Java echo + pwd retry loops
- C# env-injection `RunWithRetryAsync` wrapper
- Go `runCommandWithRetry` helper and its callers in
`TestE2E_PauseResume` and `TestFilesystem_SetPermissions`
Keep the targeted polls that cover other races not addressed by the
execd fix:
- Go `TestE2E_FullLifecycle` execd-Ping `Eventually` — bypasses the
high-level SDK and pings the server-side proxy directly; the proxy
drops the very first connection in the gap between sandbox state
Running and execd routes registering. Real users go through
`CreateSandbox`/`WaitUntilReady`, which already handles this; the
low-level test does not.
- Java/C# `waitUntilEgressBlocks` polls — egress sidecar policy is
applied asynchronously after the sandbox is marked ready, so a fixed
sleep is inherently flaky. This is a separate readiness-gating bug
that should be addressed server-side.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
0 commit comments