Commit 75c72dd
eager session-id persistence via on_session_id_resolved callback (#4078)
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:927` via `upsert_dispatch_record_by_name`). On signal
interruption (`CancelledError`), only `status`, `reason`, and `ended_at`
are written via `mark_dispatch_interrupted()`. This leaves the manifest
non-resumable because downstream consumers (state recovery, fleet
session, label cleanup) rely on `dispatched_session_id` being non-empty.
Part A addresses the core architectural immunity: eager session-identity
persistence via an `on_session_id_resolved` callback fired from inside
the live-execution task group (mirroring the existing `on_pid_resolved`
pattern), `mark_dispatch_interrupted` signature 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.jsonl` fallback lookup), R4 (L3 orchestrator dispatch history
visibility), and the remaining deferred-write fields (`token_usage`,
`resume_checkpoint`, `branch_name`).
## Requirements
`dispatched_session_id` is 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 stale `in-progress`
GitHub label, prevents the orchestrator from resuming the interrupted
session. Recovery requires manual `reset_dispatch(force=True)` followed
by a fresh dispatch, losing the ability to continue from where the
session left off.
### R1: Eager `dispatched_session_id` Write on Interruption
Extend `mark_dispatch_interrupted()` to accept an optional
`dispatched_session_id` parameter. In the `except
asyncio.CancelledError` handler at `_api.py:731`:
1. Guard against `NameError`: `skill_result` is assigned inside the
`try` block at line 698. If `CancelledError` fires BEFORE
`dispatch_food_truck()` returns, `skill_result` is never assigned. The
handler must use `skill_result = locals().get("skill_result")` or
pre-initialize `skill_result = None` before the `try` block.
2. If `skill_result` is available and has a non-empty `session_id`, pass
it to `mark_dispatch_interrupted()`.
3. Also capture `session_chain` — extend `mark_dispatch_interrupted()`
to accept an optional `session_chain` parameter, or add a separate
`set_dispatch_session_identity()` state mutation function.
This follows the same eager-write pattern used for PID capture via
`mark_dispatch_running()` and for `issue_url` capture 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_id` to the manifest immediately —
before `SubprocessResult` is fully constructed. This covers the case
where a signal arrives during the session (before
`dispatch_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` → `fleet` would violate import
layering. This must be implemented via a callback pattern:
`_run_dispatch` passes a `on_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 over `state_path` and
`effective_name` and calls a new state mutation function. This mirrors
the existing `on_pid_resolved` callback pattern at `_api.py:657-679`.
### R3: `sessions.jsonl` Fallback Lookup
When `dispatched_session_id` is empty on a resumable/interrupted
dispatch that has a non-empty `dispatch_id`, search `sessions.jsonl` for
entries matching that `dispatch_id` to 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:
- Query dispatches by `caller_session_id` (its own session) or
`campaign_id`
- See the current status, `dispatched_session_id`, `dispatched_pid`, and
`branch_name` for each dispatch
- Use this information to make informed resume/reset/skip decisions when
re-entering after a crash
This 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
- Add assertion on `dispatched_session_id` in
`TestCancelledErrorRecordsInterruptedState`
- Add test for post-completion race window: `CancelledError` after
`dispatch_food_truck()` returns but before
`upsert_dispatch_record_by_name()`
- Add test verifying `session_chain` is also captured on the
interruption path
- Add test for the resume-blocked scenario: INTERRUPTED + empty
`dispatched_session_id` + stale `in-progress` label → verify
`reset_dispatch` recovery works
Closes #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](https://claude.com/claude-code) via
AutoSkillit
<!-- autoskillit:pipeline-signature
steps=prepare_pr,run_arch_lenses,compose_pr,annotate_pr_diff,review_pr
-->
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>1 parent 16048e0 commit 75c72dd
17 files changed
Lines changed: 314 additions & 2 deletions
File tree
- src/autoskillit
- core/types
- execution
- headless
- process
- fleet
- tests
- contracts
- execution
- fleet
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
109 | 109 | | |
110 | 110 | | |
111 | 111 | | |
| 112 | + | |
112 | 113 | | |
113 | 114 | | |
114 | 115 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
175 | 175 | | |
176 | 176 | | |
177 | 177 | | |
| 178 | + | |
178 | 179 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
380 | 380 | | |
381 | 381 | | |
382 | 382 | | |
| 383 | + | |
383 | 384 | | |
384 | 385 | | |
385 | 386 | | |
| |||
483 | 484 | | |
484 | 485 | | |
485 | 486 | | |
| 487 | + | |
486 | 488 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
111 | 111 | | |
112 | 112 | | |
113 | 113 | | |
| 114 | + | |
114 | 115 | | |
115 | 116 | | |
116 | 117 | | |
| |||
262 | 263 | | |
263 | 264 | | |
264 | 265 | | |
| 266 | + | |
265 | 267 | | |
266 | 268 | | |
267 | 269 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
220 | 220 | | |
221 | 221 | | |
222 | 222 | | |
| 223 | + | |
223 | 224 | | |
224 | 225 | | |
225 | 226 | | |
| |||
352 | 353 | | |
353 | 354 | | |
354 | 355 | | |
| 356 | + | |
355 | 357 | | |
356 | 358 | | |
357 | 359 | | |
| |||
632 | 634 | | |
633 | 635 | | |
634 | 636 | | |
| 637 | + | |
635 | 638 | | |
636 | 639 | | |
637 | 640 | | |
| |||
657 | 660 | | |
658 | 661 | | |
659 | 662 | | |
| 663 | + | |
660 | 664 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
5 | 6 | | |
6 | 7 | | |
7 | 8 | | |
| |||
366 | 367 | | |
367 | 368 | | |
368 | 369 | | |
| 370 | + | |
| 371 | + | |
369 | 372 | | |
370 | 373 | | |
371 | 374 | | |
| |||
413 | 416 | | |
414 | 417 | | |
415 | 418 | | |
| 419 | + | |
| 420 | + | |
416 | 421 | | |
417 | 422 | | |
418 | 423 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
140 | 140 | | |
141 | 141 | | |
142 | 142 | | |
| 143 | + | |
143 | 144 | | |
144 | 145 | | |
145 | 146 | | |
| |||
430 | 431 | | |
431 | 432 | | |
432 | 433 | | |
| 434 | + | |
433 | 435 | | |
434 | 436 | | |
435 | 437 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
| 64 | + | |
64 | 65 | | |
65 | 66 | | |
66 | 67 | | |
| |||
146 | 147 | | |
147 | 148 | | |
148 | 149 | | |
| 150 | + | |
149 | 151 | | |
150 | 152 | | |
151 | 153 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
629 | 629 | | |
630 | 630 | | |
631 | 631 | | |
| 632 | + | |
632 | 633 | | |
633 | 634 | | |
634 | 635 | | |
| |||
678 | 679 | | |
679 | 680 | | |
680 | 681 | | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
681 | 690 | | |
682 | 691 | | |
683 | 692 | | |
| |||
710 | 719 | | |
711 | 720 | | |
712 | 721 | | |
| 722 | + | |
713 | 723 | | |
714 | 724 | | |
715 | 725 | | |
| |||
733 | 743 | | |
734 | 744 | | |
735 | 745 | | |
| 746 | + | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
736 | 752 | | |
737 | 753 | | |
738 | 754 | | |
739 | 755 | | |
740 | 756 | | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
741 | 761 | | |
742 | 762 | | |
743 | 763 | | |
| |||
894 | 914 | | |
895 | 915 | | |
896 | 916 | | |
897 | | - | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
898 | 920 | | |
899 | 921 | | |
900 | 922 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
82 | 82 | | |
83 | 83 | | |
84 | 84 | | |
| 85 | + | |
85 | 86 | | |
86 | 87 | | |
87 | 88 | | |
| |||
395 | 396 | | |
396 | 397 | | |
397 | 398 | | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
398 | 402 | | |
399 | | - | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
400 | 410 | | |
401 | 411 | | |
402 | 412 | | |
| |||
406 | 416 | | |
407 | 417 | | |
408 | 418 | | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
409 | 425 | | |
410 | 426 | | |
411 | 427 | | |
412 | 428 | | |
413 | 429 | | |
414 | 430 | | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
415 | 456 | | |
416 | 457 | | |
417 | 458 | | |
| |||
0 commit comments