audit(F4): narrow SSE_STALL_MESSAGE_RE to the emitted format#7
audit(F4): narrow SSE_STALL_MESSAGE_RE to the emitted format#7tesdal wants to merge 1 commit intophase-ab-basefrom
Conversation
|
Hey! Your PR title Please update it to start with one of:
Where See CONTRIBUTING.md for details. |
There was a problem hiding this comment.
Pull request overview
Narrows SSE stall detection in MessageV2.fromError to match the single, actual wrapSSE()-emitted timeout message format (audit finding F4), and updates/adds tests to ensure detection and rejection behavior is pinned to that format.
Changes:
- Tighten
SSE_STALL_MESSAGE_REto^SSE read timed out after \d+ms$and document intent. - Update existing SSE stall message test to use the real emitted format.
- Add tests for tag-based detection, exact-format message fallback, and rejection of previously-accepted loose matches.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| packages/opencode/src/session/message-v2.ts | Tightens the SSE stall detection regex and updates the surrounding intent comment. |
| packages/opencode/test/session/message-v2-sse-stall.test.ts | Updates/extends tests to validate exact-format matching and reject legacy/speculative variants. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Legacy substring fallback for rare paths where the tagged class gets stripped | ||
| // during cross-realm rethrow. Primary signal is `name`/`_tag` set by SSEStallError. | ||
| const SSE_STALL_MESSAGE_RE = /SSE (read|chunk) time(d out|out)/ | ||
| // Substring fallback matches the exact wrapSSE() emission format. |
There was a problem hiding this comment.
The comment says "Substring fallback" but the new regex is anchored (full-string match). This is misleading given the intent here is an exact message-format match; consider rewording to "message-based exact-match fallback" (or similar) so future readers don’t assume substring semantics.
| // Substring fallback matches the exact wrapSSE() emission format. | |
| // Message-based exact-match fallback for the wrapSSE() emission format. |
| // Substring fallback matches the exact wrapSSE() emission format. | ||
| // The primary signal is `_tag === "SSEStallError"`; this is only | ||
| // used when tag information is stripped during cross-realm rethrow. | ||
| const SSE_STALL_MESSAGE_RE = /^SSE read timed out after \d+ms$/ |
There was a problem hiding this comment.
/^...$/ in JS will also match before a final trailing newline, so this can still accept "SSE read timed out after 120000ms\n", which isn’t the emitted wrapSSE() format. If the goal is truly exact-format matching, either add a trailing negative lookahead to reject \r?\n or switch to a non-regex check that enforces both the prefix and digit+"ms" suffix without $’s newline behavior.
There was a problem hiding this comment.
Thanks for the careful review — applied fixes for comments 1 and 3.
Respectfully pushing back on this one with empirical evidence: in V8/Bun (and ECMAScript generally, without the m flag), $ is true end-of-string and does NOT match before a trailing \n. Verified in Bun 1.3.13:
const re = /^SSE read timed out after \d+ms$/
re.test("SSE read timed out after 120000ms") // true
re.test("SSE read timed out after 120000ms\n") // false
re.test("SSE read timed out after 120000ms\r\n") // false
re.test("SSE read\n timed out after 120000ms") // falseThe "$ matches before final \n" behavior is Python/Ruby semantics, not JS (in JS you'd need the m flag, which we don't set).
That said, the concern is legitimate in spirit, so I added the trailing-newline case explicitly to the regression test suite at message-v2-sse-stall.test.ts:99 and documented this behavior in a code comment. If the regex engine semantics ever change, we'll know.
| test("hasSSEStallCause: substring fallback matches the exact wrapSSE format", () => { | ||
| const taggedless = new Error("SSE read timed out after 120000ms") | ||
| const result = MessageV2.fromError(taggedless, { providerID }) |
There was a problem hiding this comment.
This test overlaps with the earlier "detects SSE stall by timeout message without SSEStallError name" (it exercises the same untagged timeout-message path). Consider either removing the duplication or repurposing this to cover a distinct edge case; also the phrase "substring fallback" is no longer accurate now that the regex is anchored to the full message.
| test("hasSSEStallCause: substring fallback matches the exact wrapSSE format", () => { | |
| const taggedless = new Error("SSE read timed out after 120000ms") | |
| const result = MessageV2.fromError(taggedless, { providerID }) | |
| test("hasSSEStallCause: exact wrapSSE-format message is detected through cause chain", () => { | |
| const taggedless = new Error("SSE read timed out after 120000ms") | |
| const outer = new Error("outer") | |
| outer.cause = taggedless | |
| const result = MessageV2.fromError(outer, { providerID }) |
d9d1848 to
cc529db
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // The primary signal is `_tag === "SSEStallError"`; this regex only | ||
| // matches when tag information is stripped during cross-realm rethrow. |
There was a problem hiding this comment.
The comment says the primary signal is _tag === "SSEStallError", but hasSSEStallCause() also treats err.name === "SSEStallError" as a primary signal. Please update the comment to mention both (name and _tag) so it accurately reflects the logic and avoids misleading future changes.
| // The primary signal is `_tag === "SSEStallError"`; this regex only | |
| // matches when tag information is stripped during cross-realm rethrow. | |
| // The primary signals are `name === "SSEStallError"` and `_tag === "SSEStallError"`; | |
| // this regex only matches when that structured error identity is stripped during | |
| // cross-realm rethrow. |
| // The previous regex /SSE (read|chunk) time(d out|out)/ matched all of these; | ||
| // the narrowed /^SSE read timed out after \d+ms$/ rejects them all. | ||
| // Any future wrapSSE format change must update the regex in lockstep. | ||
| // Note: V8's `$` without the `m` flag is true end-of-string and does NOT |
There was a problem hiding this comment.
This test comment attributes $ end-of-string behavior to V8, but Bun runs on JavaScriptCore and the behavior is specified by ECMAScript (engine-independent). Consider rephrasing to something like “In JS regexes, $ without the m flag matches only end-of-input …” to avoid implying engine-specific semantics.
| // Note: V8's `$` without the `m` flag is true end-of-string and does NOT | |
| // Note: In JS regexes, `$` without the `m` flag matches only end-of-input and does NOT |
The fallback regex previously allowed 'chunk timeout' / 'timeout' variants that no caller ever emits. The only producer is wrapSSE() in provider/provider.ts, which always emits the exact string 'SSE read timed out after Nms'. Anchors the regex and pins it to that format with a test, so any future change to wrapSSE() format forces a corresponding update here. Addresses audit finding F4 (Opus diamond review, 2026-04-22).
cc529db to
27ca380
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Review-only PR. Copilot feedback iterated across 3 rounds (final: no new comments). Final commit 27ca380 merges into local/integration-v2 via --no-ff; see LOCAL-INTEGRATION.md tracker. |
Summary
Audit finding F4 (2026-04-22 diamond audit) — the SSE stall detection regex
/SSE (read|chunk) time(d out|out)/was too loose: it matched 4 shapes but the code only ever emits one (SSE read timed out after <digits>ms, constructed inprovider.ts:79).This PR narrows the regex to
/^SSE read timed out after \d+ms$/so any future change to thewrapSSE()emission format must update the regex in lockstep.Changes
packages/opencode/src/session/message-v2.ts— regex narrowed + explanatory comment pinning intent to the sole producer.packages/opencode/test/session/message-v2-sse-stall.test.ts:"SSE chunk timeout"string to the actual emitted format, since the old string can no longer reach the regex path. Negative coverage for that legacy string is preserved in the new rejection tests.Verification
bun test test/session/message-v2-sse-stall.test.ts→ 9 pass / 0 failbun typecheck→ cleanReview-only PR
This PR targets
phase-ab-baseon the fork for Copilot review. It will not be merged here; the commit merges into the local audit-remediation integration branch (local/integration-v2) via--no-ffafter Copilot feedback is addressed.