Repository navigation
fix(responses,claude): detect deferred V1 collaboration tools and bound the picker upload drain - #7010
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to The stale plan example does not affect the implemented behavior. The change is mergeable after normal checks. Pre-merge checks ·
|
|
✅ Deterministic PR hygiene checks passed. |
80f886c to
dce81dc
Compare
e831e86 to
fe6768c
Compare
…tools> fragment Codex defers the V1 collaboration tools whenever the model supports tool search, which every OpenCodex catalog row does. The code-mode exec description then lists none of them (#6977 reads only the rendered case), so collabSurface() stayed null and V1 Max/Ultra turns never received the proactive delegation guidance. When its deferred_tool_world_state feature is on (Codex Desktop sessions receive it), codex-rs sends a developer <tools> fragment listing the deferred namespaces, then Added/Removed diffs on later turns. deferred-collab-surface.ts replays those fragments in input order from developer messages only and treats a current multi_agent_v1 row as V1 evidence, beside a genuine code-mode exec or a structured tool_search host. Omitted rows never establish a namespace; omitted removals and malformed fragments clear what was carried. The evidence combines with the direct and rendered surfaces exactly like #6977: it never yields v2 and yields to any contradicting surface. Clients without the feature still carry no V1 evidence, and the row cannot show whether an embedder's allowlist dropped spawn_agent alone; both limits are documented in structure/subagents.md. Refs #6844
#6986 made the picker's TCP front keep reading and discarding client ciphertext after the backend ends a connection, so an HTTP/1.1 refusal, generated 502 or early reply sent with Connection: close is not reset before the client reads it. That discard had no end: a client that keeps trickling an abandoned upload held the connection until listener shutdown. Once the backend has ended and the reply has flushed to the client socket (finish), the front now arms a drop after PICKER_ABANDONED_INPUT_LINGER_MS (30 s); a connection that closes first cancels it. Arming after the flush keeps the reply and its Connection: close behaviour intact. The timer is injected through a test seam, so claude-picker-linger.test.ts fires it deterministically with a half-open TLS client that never stops uploading; the existing claude-picker-upload cases are unchanged. Refs #6984
dce81dc to
ed2e435
Compare
There was a problem hiding this comment.
🔇 Additional comments (15)
src/claude/intercept/picker-listener.ts (2)
490-500: Guard the linger against a timer that arms after the connection is already closing.At Line 494, the guard checks only
client.destroyed. Acloseevent can fire after that check and beforefinish. In that case,client.once("close", cancel)at Line 496 never runs, becauseclosehas already been emitted. The timer then fires 30 seconds later and callsclient.destroy()on a socket that is already closed. That call is a no-op and the timer is short-lived. The risk is low, and thedestroyedcheck covers the common case.The
finishpath at Line 499 is the only path where a close can arrive before the callback runs. Node emitsfinishbeforeclose, so the order is safe in practice. No change is required.
493-497: Remove thefinishlistener when the client closes before the reply is flushed.At Line 499,
client.once("finish", linger)stays registered ifclientcloses without emittingfinish. For example, a reset destroys the socket and nofinishevent follows. The listener is garbage-collected with the socket, so nothing leaks. Nolingercall occurs after destruction, because theclient.destroyedguard at Line 494 covers that case. No change is required.devlog/_plan/261013_deferred_v1_collab_surface/020_6984_verification.md (1)
1-1: LGTM!structure/clients/claude-desktop.md (1)
234-248: LGTM!tests/claude-integration/claude-picker-linger.test.ts (1)
1-121: LGTM!devlog/_plan/261013_deferred_v1_collab_surface/000_plan.md (1)
1-81: LGTM!src/server/responses/deferred-collab-surface.ts (1)
1-158: LGTM!I checked the following cases against the code:
- The opening and closing tag checks reject text that only mentions the tag.
parseFragmentrejects a repeated group, an Added group after Removed, an empty group, and content after the empty-state line.ROWuses[\s\S]so U+2028 and U+2029 stay opaque.- The replay switch handles
malformed,snapshot, anddiff. Omitted removals clear before the explicit adds.- Line 140 clears on the empty-state notice for both snapshot and diff.
The OpenGrep hint at Line 99 is a false positive.
ROW.execisRegExp.prototype.exec, notchild_process.exec.src/server/responses/code-mode-collab-surface.ts (1)
13-18: LGTM!Also applies to: 28-36, 176-195, 197-204
src/server/responses/collaboration.ts (1)
12-13: LGTM!The rendered-only paths match the old behavior:
- Rendered V1 with a foreign claim returns
null.- Rendered V1 alone follows the same direct-surface rule as before.
- No inferred V1 returns
direct.surface.Also applies to: 322-330
structure/subagents.md (1)
451-469: LGTM!tests/codex-integration/deferred-collab-surface.test.ts (1)
1-292: LGTM!The tests cover the following, and the continuation suite isolates
OPENCODEX_HOME:
- Grammar and replay.
- Budget omissions.
- Host gates.
- Surface combination.
- Continuations.
scripts/test-layout/layout.json (1)
23-23: LGTM!Also applies to: 1247-1247
tests/fixtures/test-layout-expected.json (1)
32-32: LGTM!Also applies to: 790-790
docs-site/src/content/docs/guides/sub-agent-surface.md (1)
131-135: 📐 Maintainability & Code QualityThe translated pages do not state that deferred V1 tools are always undetectable. They describe general V1 guidance behavior and omit the English page's code-mode detail, but that omission does not contradict the updated English paragraph.
devlog/_plan/261013_deferred_v1_collab_surface/010_deferred_v1_evidence.md-66-74 (1)
66-74: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
⚠️ Unverified finding
Verification ran but could not confirm this finding. It is shown for review, not as a verified issue.Update the plan: its
deferredV1Evidencesignature andcollabSurface()snippet no longer match the implementation.The plan shows
deferredV1Evidence(parsed)(Line 20) and calls it with one argument (Line 70). The implemented function insrc/server/responses/deferred-collab-surface.tsLine 154 takes(parsed, codeMode). The plan also says the helper callsanalyzeCodeModeExecitself (Line 23). The implementation receives that analysis fromcollabSurface(). A later reader who follows this plan will reintroduce a second scan of the exec tool description.Update the signature and the call site in the plan to match the code.
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml
- Review profile: ASSERTIVE
- Plan: Advanced
- Run ID:
d3665632-f4a4-492b-a956-8a85560b34d1
📒 Files selected for processing (14)
devlog/_plan/261013_deferred_v1_collab_surface/000_plan.mddevlog/_plan/261013_deferred_v1_collab_surface/010_deferred_v1_evidence.mddevlog/_plan/261013_deferred_v1_collab_surface/020_6984_verification.mddocs-site/src/content/docs/guides/sub-agent-surface.mdscripts/test-layout/layout.jsonsrc/claude/intercept/picker-listener.tssrc/server/responses/code-mode-collab-surface.tssrc/server/responses/collaboration.tssrc/server/responses/deferred-collab-surface.tsstructure/clients/claude-desktop.mdstructure/subagents.mdtests/claude-integration/claude-picker-linger.test.tstests/codex-integration/deferred-collab-surface.test.tstests/fixtures/test-layout-expected.json
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.
Ten more commits landed on dev (lidge-jun#7010, lidge-jun#7011). The merge is automatic and none of them touch gui/, src/oauth/, src/providers/ or src/server/management/, the areas this branch changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Two proxy fixes stacked on the owner bundle.
33747c4188(refs [Bug] V1 proactive guidance is skipped when subagent tools are hidden behind code mode #6844): Codex defers V1 subagent tools behind tool search, so nothing namedspawn_agentis declared and the proactive delegation guidance was skipped. OpenCodex now reads the deferred namespaces from the<tools>fragment Codex Desktop sends, positive evidence only, never inferring V2, and yielding to any contradicting surface. Clients that do not send that fragment remain undetectable; documented instructure/subagents.md.5e943bfe69(refs Claude picker relay: HTTP/1.1 refusal of an unfinished upload hangs (Bun 1.4.2) or resets (Bun 1.4.3) #6984): after fix(ci): run the test runner on Bun 1.4.3 so spawnSync stops losing child exits (#4956) #6986, the picker front kept discarding an abandoned HTTP/1.1 upload with no end once the reply was written. It now drops the connection 30 s after the reply has flushed if the client is still sending; close or shutdown cancels the timer. Reply delivery andConnection: closeare unchanged.Verification
3441130ea8(Bun 1.4.3 test runner, external checkout):bun run typecheck,structure:check,privacy:scan,skill:surface:checkexit 0; guards (test-layout, layout tooling with the pinned runner, file-size ratchet, repo hygiene, skill-ocx, structure-ssot) 110 pass / 0 fail; GUI locale parity 27 pass;lint:guiandbuild:guiexit 0;scripts/test-layout/layout.json1999 lines.Checklist
Summary by CodeRabbit
multi_agent_v1as a deferred tool can now receive proactive collaboration guidance, when the request also confirms support.