fix(claude-swe-agent): stop a fast turn from racing the Remote Control session URL (#183) - #191
Merged
Merged
Conversation
…l session URL (#183) The "watch live / take over the session" link is posted for most runs but dropped intermittently on the `ai-review` flow (issue #183). Root cause is a timing race in `runClaudeTurnRemoteControlled`'s poll loop: - the session URL is emitted the instant the transcript's `bridgeSessionId` line appears, and - that line is written when the bridge's ASYNC registration handshake with claude.ai completes -- not necessarily before the turn does. A long triage turn registers the bridge many polls before it finishes, so the URL is always emitted first. A fast, read-only review can finish in the same poll window the bridge is still registering in; the loop returned the instant it saw `turn_duration`, so which of the two the transcript flushed first decided whether the link was ever emitted. That is the non-deterministic "sometimes it fails to" the maintainer observed. Make it deterministic: when a turn completes and the URL has not been reported yet, hold the finished turn open for a bounded grace (`REMOTE_CONTROL_URL_GRACE_MS`, 15s, overridable) and keep re-reading the transcript so a just-registered bridge still surfaces its link before returning. Skipped entirely on the common path where the bridge registered mid-turn, so it costs nothing there; bounded so a bridge that never registers can't hang a completed review. Adds a `latebridge` fake-script mode and a regression test asserting the URL is still reported when the `bridgeSessionId` line lands after `turn_duration`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FLZRLE1mqh9ajMYyKyaWSr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #183 — the "🤖 Starting work on this now. Watch live or take over the session here…" comment is posted for most runs but dropped intermittently on the
ai-reviewflow. The maintainer's note nails it: "most of the time it does. Sometimes it fails to … This should be deterministic logic." It is a timing race, and this makes it deterministic.Root cause
In
runClaudeTurnRemoteControlled(apps/claude-code-swe-agent/src/claude-runner.ts), the poll loop:bridgeSessionIdline appears (remote-control-urlprogress event → gateway posts the comment), andturn_durationentry (turn finished).The
bridgeSessionIdline is written when the Remote Control bridge's async registration handshake with claude.ai completes — which is not guaranteed to happen before the turn does. A long triage turn registers the bridge many polls before it finishes, so the URL is always emitted first. A fast, read-only review can finish in the same poll window the bridge is still registering in — and returning the momentturn_durationlands drops the link. Which of the two the transcript flushed first is a race, so the link appears "most of the time" and vanishes on the quick review turns. (ai-reviewruns useclaude-code-swe-agentwith Remote Control; e.g. AgentRunfc9f0896referenced in the code.)Everything downstream (agent-runtime → orchestrator
remoteControlUrlListener→ gatewayonRemoteControlUrl) is already deterministic and ordered — the URL is only ever dropped because it was never emitted in this race.Fix
When a turn completes and the URL has not been reported yet, hold the finished turn open for a bounded grace (
REMOTE_CONTROL_URL_GRACE_MS, 15s, overridable viaurlGraceMs) and keep re-reading the transcript so a just-registered bridge still surfaces its link before returning.finallykill, so registration keeps progressing throughout the wait.Tests
Adds a
latebridgefake-script mode (writesturn_durationfirst, then appends thebridgeSessionIdline ~60ms later) and a regression test asserting the URL is still reported when the bridge line lands after completion. Verified the test fails against the old return-on-completion behavior and passes with the fix;tsc --noEmitis clean.Maintainer: apply the
ai-reviewlabel to this PR to request an automated code review, or theai-triagelabel to have that review feedback addressed and the branch brought back in sync with its base. (The automation can't label its own PR, so this needs a human.)🤖 Generated with Claude Code
https://claude.ai/code/session_01FLZRLE1mqh9ajMYyKyaWSr