Skip to content

fix(server): steer a turn only when one is actually running - #264

Merged
rynfar merged 3 commits into
pylonfrom
fix/steer-requires-active-turn
Sep 4, 2026
Merged

fix(server): steer a turn only when one is actually running#264
rynfar merged 3 commits into
pylonfrom
fix/steer-requires-active-turn

Conversation

@rynfar

@rynfar rynfar commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Problem

Messages sent to a Claude thread that showed "Working" could vanish. Claude Code emits system/status notifications between turns (for example when a background task finishes). The adapter maps those to running, so the projected session sat in running with no active turn and no active request id, sometimes for hours.

The decider treated any running session as steerable and admitted the next user message as a steer with no pending admission. The adapter started a real turn for it, but ingestion's strict admission gate discards turn-scoped events whose admission request id does not match the session's active request id, which was undefined. The turn ran to completion in Claude and none of it was persisted: no turn start, no assistant text, no activities, no error.

Traced from the nightly database and provider logs: seven such admissions across three threads since September 2, each a silently lost response. Both halves of the admission model are Pylon-only (8d3fd193d7, 3ba49ed5a2), so this is not an upstream regression.

Fix

The decider now picks steer only when the session is running and has an active turn. Otherwise it issues an exact start with a pending admission, which the reactor already handles for a live session and which ingestion correlates through the existing CAS accept path.

Added a decider test that reproduces the exact state from the incident (running, activeTurnId: null, no active request id) and asserts a start intent with a pending starting session-set.

Second commit

A running turn the provider opened on its own (Claude continuing after a background task) has no admission request id either. Steering it tags the provider's next turn with an id the gate cannot correlate, so the same loss happens. Under an incarnation-tracked session every admitted turn carries its request id, so a running turn without one now gets an exact start too. Sessions without an incarnation predate admission tracking and keep plain steering, which the existing ingestion tests cover.

Verification

  • vp test run on decider, command invariant, provider command reactor, and provider runtime ingestion tests: 17 files, 267 passed
  • vp run -F t3 typecheck: clean
  • vp fmt --check and vp lint on the touched files: clean

Follow-ups (separate PRs)

  • Stop flipping a session to running on system/status when no turn is active. That is what makes the sidebar say "Working" for an idle thread.
  • Decide whether Claude's synthetic turns (background-task continuations) should be admitted or stay hidden. Their output is currently dropped by the same gate.

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:S 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 +18 B (+0.1%) 15.1 KiB
Codex Thread snapshot wire 7.1 KiB 7.1 KiB +6 B (+0.1%) 7.3 KiB
Codex Live turn WebSocket wire 6.5 KiB 6.6 KiB +12 B (+0.2%) 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 −22 B (−0.2%) 15.1 KiB
Claude Thread snapshot wire 7.1 KiB 7.1 KiB −9 B (−0.1%) 7.3 KiB
Claude Live turn WebSocket wire 6.6 KiB 6.5 KiB −13 B (−0.2%) 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: 2e7d0fb · 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 5ba3ac7 into pylon Sep 4, 2026
18 checks passed
@rynfar
rynfar deleted the fix/steer-requires-active-turn 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:S 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