eager session-id persistence via on_session_id_resolved callback#4078
Merged
Trecek merged 1 commit intoJun 12, 2026
Merged
Conversation
…#4072) The dispatch system silently dropped dispatched_session_id (and related identity fields) on CancelledError: only status/reason/ended_at were written via mark_dispatch_interrupted(). The session ID was known in acc.stdout_session_id/acc.channel_b_session_id inside the task group but never persisted to state. Add an on_session_id_resolved callback fired from _extract_stdout_session_id inside the live-execution task group, mirroring the existing on_pid_resolved pattern. The closure calls mark_dispatch_session_identity to eagerly persist the session ID while the dispatch is still RUNNING. Three layers of defense: 1. Eager callback writes to state during execution 2. CancelledError handler passes captured_session_id + marker_dir to mark_dispatch_interrupted (additive — only writes empty fields) 3. Normal completion path prefers eagerly-captured value over skill_result mark_dispatch_interrupted now accepts optional dispatched_session_id, session_chain, and dispatched_session_log_dir. New mark_dispatch_session_identity function is idempotent and status-guarded (RUNNING only). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4c2e32d to
fafa177
Compare
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.
The dispatch system has a structural weakness where runtime-discovered identity fields (
dispatched_session_id,session_chain,dispatched_session_log_dir,token_usage,resume_checkpoint,branch_name) are only written to state on the normal completion path (_api.py:927viaupsert_dispatch_record_by_name). On signal interruption (CancelledError), onlystatus,reason, andended_atare written viamark_dispatch_interrupted(). This leaves the manifest non-resumable because downstream consumers (state recovery, fleet session, label cleanup) rely ondispatched_session_idbeing non-empty.Part A addresses the core architectural immunity: eager session-identity persistence via an
on_session_id_resolvedcallback fired from inside the live-execution task group (mirroring the existingon_pid_resolvedpattern),mark_dispatch_interruptedsignature extension for defense-in-depth identity capture, and test coverage that makes this bug class instantly caught. Part B (separate task) will cover R3 (sessions.jsonlfallback lookup), R4 (L3 orchestrator dispatch history visibility), and the remaining deferred-write fields (token_usage,resume_checkpoint,branch_name).Requirements
dispatched_session_idis recorded as""(empty string) in the dispatch manifest when a signal interrupts the dispatch — even when the headless session completed all pipeline steps successfully. This makes the dispatch non-resumable and, combined with the stalein-progressGitHub label, prevents the orchestrator from resuming the interrupted session. Recovery requires manualreset_dispatch(force=True)followed by a fresh dispatch, losing the ability to continue from where the session left off.R1: Eager
dispatched_session_idWrite on InterruptionExtend
mark_dispatch_interrupted()to accept an optionaldispatched_session_idparameter. In theexcept asyncio.CancelledErrorhandler at_api.py:731:NameError:skill_resultis assigned inside thetryblock at line 698. IfCancelledErrorfires BEFOREdispatch_food_truck()returns,skill_resultis never assigned. The handler must useskill_result = locals().get("skill_result")or pre-initializeskill_result = Nonebefore thetryblock.skill_resultis available and has a non-emptysession_id, pass it tomark_dispatch_interrupted().session_chain— extendmark_dispatch_interrupted()to accept an optionalsession_chainparameter, or add a separateset_dispatch_session_identity()state mutation function.This follows the same eager-write pattern used for PID capture via
mark_dispatch_running()and forissue_urlcapture added in PR #3828.R2: Intermediate Session ID Write at Process Layer
When Channel B or stdout extraction succeeds during the race monitoring phase, write
dispatched_session_idto the manifest immediately — beforeSubprocessResultis fully constructed. This covers the case where a signal arrives during the session (beforedispatch_food_truck()returns) but the session ID is already known.Architectural constraint: The execution package is IL-1; fleet state is IL-2. Direct imports from
execution→fleetwould violate import layering. This must be implemented via a callback pattern:_run_dispatchpasses aon_session_id_resolved: Callable[[str], None]callback down to the process runner, which invokes it when Channel B or stdout extraction succeeds. The callback closes overstate_pathandeffective_nameand calls a new state mutation function. This mirrors the existingon_pid_resolvedcallback pattern at_api.py:657-679.R3:
sessions.jsonlFallback LookupWhen
dispatched_session_idis empty on a resumable/interrupted dispatch that has a non-emptydispatch_id, searchsessions.jsonlfor entries matching thatdispatch_idto recover the session_id. Defense-in-depth, not a substitute for R1/R2.R4: L3 Orchestrator Dispatch History Visibility
Give the L3 fleet dispatch orchestrator read access to the history of dispatches it launched and their current status. Currently, the L3 has no mechanism to query dispatch state — it relies entirely on the return value of
dispatch_food_truck, which is lost on interruption. The orchestrator should be able to:caller_session_id(its own session) orcampaign_iddispatched_session_id,dispatched_pid, andbranch_namefor each dispatchThis would eliminate the class of bugs where the L3 orchestrator has no context about what happened during an interrupted dispatch and must rely on indirect signals (labels, sidecar files) that may themselves be stale.
R5: Test Coverage
dispatched_session_idinTestCancelledErrorRecordsInterruptedStateCancelledErrorafterdispatch_food_truck()returns but beforeupsert_dispatch_record_by_name()session_chainis also captured on the interruption pathdispatched_session_id+ stalein-progresslabel → verifyreset_dispatchrecovery worksCloses #4072
Implementation Plan
Plan file:
/home/talon/projects/autoskillit-runs/remediation-20260611-155102-572761/.autoskillit/temp/rectify/rectify_dispatch_session_id_signal_persistence_2026-06-11_155102.md🤖 Generated with Claude Code via AutoSkillit
Token Usage Summary
* Step used a non-Anthropic provider; caching behavior may differ.
Token Efficiency
Model Usage Breakdown