Skip to content

fix(claude): between-turn status and resume handshake no longer touch the turn lifecycle - #265

Merged
rynfar merged 1 commit into
pylonfrom
fix/claude-between-turn-signals
Sep 4, 2026
Merged

fix(claude): between-turn status and resume handshake no longer touch the turn lifecycle#265
rynfar merged 1 commit into
pylonfrom
fix/claude-between-turn-signals

Conversation

@rynfar

@rynfar rynfar commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Problem

Two Claude Code SDK messages that are not part of any turn were being projected as turn lifecycle, and both were traced from the nightly database and provider logs on 2026-09-04:

  1. system/status between turns. When a background task finishes, the CLI re-invokes the model and reports status: requesting before any assistant output. The adapter mapped that to session.state.changed: running with no turn id, so the projected session sat in running with no active turn, sometimes for hours. The sidebar showed "Working" for an idle thread, and until fix(server): steer a turn only when one is actually running #264 the next message was admitted as a steer into nothing.
  2. The resume handshake result inside a real turn. A resumed session replays system/init plus result(num_turns: 0). When that landed after the user's first prompt was already in flight, the adapter completed the user's turn after two seconds with no output. The actual answer then arrived as a synthetic turn. In the incident the visible turn showed nothing and the real work ran hidden.

Fix

  • case "status" emits a lifecycle event only while a turn is open. Between turns it is the CLI's own request cycle, and the assistant output that follows opens its own synthetic turn.
  • handleResultMessage ignores a zero-turn success result when the open turn has produced no items, no assistant text, and has no tool in flight. A real turn always makes at least one request, so that shape can only be the handshake. Failed or non-empty results complete as before. A log line (claude.turn.handshake-result-inside-turn) keeps the trigger measurable.

Upstream has identical code on both paths, so this is Pylon getting ahead rather than catching up. Upstream's #9135 (SDK 0.3.260, terminal-reason failure handling) is unrelated and still worth adopting through the upstream batch.

Verification

  • Two new adapter tests, each watched failing first: the between-turn status produced a second lifecycle event, and the handshake result produced a second turn start and completion.
  • vp test run on ClaudeAdapter.test.ts: 85 passed.
  • vp run -F t3 typecheck: clean. vp fmt --check and vp lint on the touched files: clean.

Companion PRs: #264 (decider no longer steers unadmitted turns) and the follow-up that admits provider-initiated turns in ingestion.

Model: Claude Fable 5.1. Harness: Pylon (Claude Code provider).


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

@github-actions github-actions Bot added size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.6 KiB 13.6 KiB +10 B (+0.1%) 15.1 KiB
Codex Thread snapshot wire 7.1 KiB 7.1 KiB +1 B (+0.0%) 7.3 KiB
Codex Live turn WebSocket wire 6.5 KiB 6.5 KiB +9 B (+0.1%) 7.8 KiB
Codex Live turn WebSocket decoded 56.5 KiB 56.5 KiB 0 B (0.0%) 66.4 KiB
Codex Live turn messages 8 8 0 (0.0%) 21
Claude Total thread wire 13.6 KiB 13.6 KiB −12 B (−0.1%) 15.1 KiB
Claude Thread snapshot wire 7.1 KiB 7.1 KiB −2 B (−0.0%) 7.3 KiB
Claude Live turn WebSocket wire 6.6 KiB 6.5 KiB −10 B (−0.1%) 7.8 KiB
Claude Live turn WebSocket decoded 57.3 KiB 57.3 KiB 0 B (0.0%) 66.4 KiB
Claude Live turn messages 8 8 0 (0.0%) 21

Baseline: bfc5631 · PR result: 6f4a03c · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 111.2 KiB
  • Claude decoded thread snapshot: 111.9 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@rynfar
rynfar merged commit 40ce05f into pylon Sep 4, 2026
17 checks passed
@rynfar
rynfar deleted the fix/claude-between-turn-signals branch September 4, 2026 17:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant