Repository navigation
fix(native-chat): release sessions after provider root exit - #20502
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (1)
💤 Files with no reviewable changes (1)
Included review availability: Your plan provides up to 10 included reviews per hour; 1 remains after this review. 📝 WalkthroughWalkthroughThe change adds agent-session creation tracing with phase timing and span attributes. Claude acquisition and cleanup now use centralized resolution and classified exit errors. Structured renderer launches no longer fall back to legacy terminal surfaces after refusal or deadline outcomes. Launch state moves to a registry, callers use structured settlement results, and pending creation and recovery messages identify the selected agent. Priority: ➖ Normal Merge Risk: 🟡 Moderate · up to A stalled structured launch can remain pending without a usable recovery surface. Add a bounded settlement or equivalent recovery path before merging. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 22.03% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 59 functions across 47 files. (1 skipped: 1 unsupported.) Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: dfc60991-5464-4acb-b58c-36978faf7487
📒 Files selected for processing (22)
IMPLEMENTATION-NOTES.mdsrc/main/claude/claude-structured-acquisition-launch.tssrc/main/claude/claude-structured-session-acquisition.tssrc/main/native-chat/agent-session-wire/structured-agent-session-acquisition-options.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-acquisition.tssrc/main/native-chat/agent-session-wire/structured-agent-session-adapter.tssrc/main/native-chat/agent-session-wire/structured-agent-session-attach-flow.tssrc/main/native-chat/agent-session-wire/structured-agent-session-attach-orchestration.tssrc/main/observability/agent-session-instrumentation.tssrc/main/observability/instrumentation.test.tssrc/renderer/src/lib/launch-work-item-direct-agent-routing.test.tssrc/renderer/src/lib/launch-work-item-direct-agent-routing.tssrc/renderer/src/lib/pending-worktree-creation.test.tssrc/renderer/src/lib/pending-worktree-creation.tssrc/renderer/src/lib/structured-agent-launch-settlement.test.tssrc/renderer/src/lib/structured-agent-launch-settlement.tssrc/renderer/src/lib/structured-agent-session-launch-callers.tssrc/renderer/src/lib/structured-agent-session-launch-deadline-fallback.test.tssrc/renderer/src/lib/structured-agent-session-launch-registry.tssrc/renderer/src/lib/structured-agent-session-launch.tssrc/renderer/src/lib/worktree-creation-structured-session.test.tssrc/renderer/src/lib/worktree-creation-structured-session.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.
There was a problem hiding this comment.
Important
The new deadline-then-legacy settlement is only understood by two of its consumers, so a slow launch now takes the wrong path in the full-create, folder-workspace, new-tab, and fork flows.
Reviewed changes
- Phase-timed
agentSession.createtracing — newagent-session-instrumentation.tswraps create-intent attaches once and threads a phase recorder through orchestration → attach flow → acquisition → adapter → Claude acquisition; attributes carry only closed phase names and timings. starting-chatpending phase —WorktreeCreationPhasegainsstarting-chat, worktree creation sets it immediately before launch, andgetCreationProgressLabelnames the pending agent.- 30s deadline fallback —
settleStructuredAgentLaunchraces the launch against an injectable deadline that claims the shared fallback asdeadline-then-legacy, keeps cancellation ahead of it, and retires a late structured publication. - Generalized fallback claim —
claimFallbackreplacesclaimDefinitiveRefusalFallback(kept as a deprecated alias); the callers group only transitions frompending/unknown, and the worktree session retires late structured sessions. - Registry extraction / Claude refactor — launch registry and status hooks move to
structured-agent-session-launch-registry.ts; Claude launch resolution is extracted to stay under the line cap.
ℹ️ Nitpicks
IMPLEMENTATION-NOTES.mdis a 132-line agent work log added at the repo root. It reads like a scratch plan/ablation record rather than repo documentation; consider dropping it from the PR (or moving the durable parts intodocs/reference/).
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
…hat-launch-visibility # Conflicts: # src/main/native-chat/agent-session-wire/structured-agent-session-attach-flow.ts
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
The delta since the prior pullfrog review (643dcf9) is the merge commit 348149f and a test-hygiene follow-up b053abe. It resolves the prior [!IMPORTANT] finding that deadline-then-legacy was only understood by two consumers.
- Centralized legacy-fallback interpretation — new
structuredAgentLegacyFallbackFromSettlementnormalizesrefused-then-legacyanddeadline-then-legacyinto one result, and full-create, folder-workspace create, new-tab, fork, and direct-routing all consume it instead of branching on the refusal kind alone. - Universal late retirement — the per-flow
onStructuredLatehook is gone; the shared settlement loop calls the newretireStructuredAgentSessionSurfacewhenever the fallback ran, and registers it before the post-fallback cancellation check, so a structured publication that lands after the fallback is retired in every flow. - Census ratchet — a new test asserts no production file outside the settlement module names the legacy kinds directly; confirmed by grep (only the settlement module and tests match).
- Cleanup —
IMPLEMENTATION-NOTES.mdis deleted and post-merge test hygiene restored the hoisted mocks and formatting.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Commit 04673918 ("Make structured fallback settlement exhaustive") landed after the previous review; it tightens the legacy-fallback API rather than changing behavior.
- Type-narrowing guard — added
isStructuredAgentLegacyFallbackSettlement, and overloadedstructuredAgentLegacyFallbackFromSettlementso a settlement narrowed by the guard returns a non-null result. - Exhaustive consumers —
launch-work-item-direct-agent-routing.tsandworktree-creation-structured-session.tsnarrow first, then consume the fallback without optional chaining, and drop theirdefault/ mergedfailedbranches. The remaining switches cover every non-legacy kind, so a future settlement kind becomes a compile error instead of a silent fallthrough.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: dc85e667-0e6f-492d-a464-8a4feb2dc132
📒 Files selected for processing (35)
src/main/claude/claude-structured-session-close.test.tssrc/main/claude/claude-structured-session-close.tssrc/main/native-chat/agent-session-wire/structured-agent-session-claude-root-exit.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-eviction.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-eviction.tssrc/renderer/src/components/sidebar/folder-workspace-composer-submit.tssrc/renderer/src/components/terminal-pane/terminal-agent-session-fork.test.tssrc/renderer/src/components/terminal-pane/terminal-agent-session-fork.tssrc/renderer/src/hooks/composer-state/full-creation-execution.tssrc/renderer/src/hooks/composer-state/full-creation-structured-launch.test.tssrc/renderer/src/hooks/composer-state/full-creation-structured-launch.tssrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/lib/launch-agent-in-new-tab-structured.test.tssrc/renderer/src/lib/launch-agent-in-new-tab-structured.tssrc/renderer/src/lib/launch-agent-in-new-tab.tssrc/renderer/src/lib/launch-agent-structured-chat-guard.test.tssrc/renderer/src/lib/launch-work-item-direct-agent-routing.test.tssrc/renderer/src/lib/launch-work-item-direct-agent-routing.tssrc/renderer/src/lib/launch-work-item-direct.tssrc/renderer/src/lib/onboarding-folder-agent-launch.tssrc/renderer/src/lib/structured-agent-launch-no-terminal-fallback.test.tssrc/renderer/src/lib/structured-agent-launch-settlement-caller-census.test.tssrc/renderer/src/lib/structured-agent-launch-settlement.test.tssrc/renderer/src/lib/structured-agent-launch-settlement.tssrc/renderer/src/lib/structured-agent-session-launch-callers.tssrc/renderer/src/lib/structured-agent-session-launch-failure-toast.tssrc/renderer/src/lib/structured-agent-session-launch-refusal-fallback.test.tssrc/renderer/src/lib/structured-agent-session-launch-registry.tssrc/renderer/src/lib/structured-agent-session-launch.test.tssrc/renderer/src/lib/structured-agent-session-launch.tssrc/renderer/src/lib/worktree-creation-flow-execute.tssrc/renderer/src/lib/worktree-creation-structured-recovery.tssrc/renderer/src/lib/worktree-creation-structured-session.test.tssrc/renderer/src/lib/worktree-creation-structured-session.tssrc/renderer/src/lib/worktree-creation-structured-unknown-outcome.test.ts
💤 Files with no reviewable changes (5)
- src/renderer/src/components/terminal-pane/terminal-agent-session-fork.ts
- src/renderer/src/hooks/composer-state/full-creation-structured-launch.ts
- src/renderer/src/lib/structured-agent-launch-settlement-caller-census.test.ts
- src/renderer/src/lib/structured-agent-session-launch-refusal-fallback.test.ts
- src/renderer/src/components/terminal-pane/terminal-agent-session-fork.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/renderer/src/lib/structured-agent-session-launch-registry.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 1 remains after this review.
|
@coderabbitai review |
|
There was a problem hiding this comment.
ℹ️ No critical issues — one documentation note inline.
Reviewed changes
Commit b4432ecee2 reworks the PR into a root-cause fix and removes the deadline/terminal-fallback design the earlier reviews covered. That commit is the delta since the prior pullfrog review at 04673918c3; the rest of the branch is unchanged.
- Provider root-exit releases the lease —
claude-structured-session-close.tsclassifies an unproven published-session close and an unproven acquisition cancel throughclaudeAcquisitionCleanupError, throwingAgentSessionAcquisitionRootExitObservedError(root: exited,tree: unverifiable) orAgentSessionPreSpawnError(processless) instead of returningfalse. Only a genuinely unproven exit still returnsfalse. - Eviction continues on actionable verdicts —
structured-agent-session-eviction.tsswallows those two errors and runs the remaining steps (drain → sink teardown →release-lease→forget); every other throw, including…ExitUnprovenError, still aborts so the next close is a real retry. - Deadline and terminal fallback removed —
structured-agent-launch-settlement.tsdropslegacyFallback/claimFallback/STRUCTURED_AGENT_LAUNCH_DEADLINE_MS, the caller-group refusal settlement, the late-surface retirement module, and the terminal fallback in every renderer entrypoint; a definitive refusal now settles asfailed. - Agent-named unknown recovery —
markStructuredWorktreeLaunchUnconfirmedtakes the narrowed provider and the unknown toast interpolates{{value0}};en.jsonand its tests were updated. - Census ratchet — a new test asserts no production renderer file names the fallback kinds or the deadline constant.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
Independent hidden-CDP QA — structured chat starting visibilityValidated on worktree The production pending row and creation panel name the selected agent and keep the elapsed counter visible: Claude — pending row + panel, elapsed 57s Codex — pending row + panel, elapsed 11s Renderer console: 0 errors (unrelated Tooltip / React Grab warnings only). No production files changed. Method note: the starting-chat phase was held through the real store APIs ( Local evidence: |
Review statusPASS WITH RISK — no valuable code fixes required.
Residual risk: this session did not exercise a live provider-backed create, real SSH/relay/network-failure paths, Windows/WSL runtime, or the full local suite. GitHub’s No commit or push was necessary. |
|
Merge conflicts resolved and pushed in commit Resolved conflicts in Claude structured acquisition and acquisition-options tests while preserving the PR's launch lifecycle/instrumentation changes and current-main fast-mode/session-option behavior. Focused tests (25) and |
…hat-launch-visibility
|
Too many files changed for review (124 files, 100 file limit). Bypass the limit by tagging |
…hat-launch-visibility
There was a problem hiding this comment.
Important
This delta rebases the structured-chat launch/close lifecycle onto main's surface-based launch API. Two behavioral gaps come with it: a closed published chat can be re-mirrored because only provisional closes get a cancellation tombstone, and a caller coalescing onto a retained failed launch has its prompt silently dropped.
Reviewed changes
Incremental delta 7e7125dcf9..a145a79dab — the dc9c77c8e8 "fix native chat launch visibility" reconciliation (87 files) plus the a145a79dab merge of origin/main (b61a2347b9).
- Adopted main's
{ surface }launch result everywhere — new-tab, work-item direct, fork, quick launch, source-control recovery, onboarding, worktree creation, full-create, and folder-workspace entrypoints now consumeLaunchAgentInNewTabResult.surfaceand threadbeforeSurfaceOpen, so a workspace can reveal before its surface opens. - Extracted the launch state machine —
structured-agent-session-launch-registry.tsplus newstructured-agent-session-launch-persistence.ts(localStorage launch records and session-scoped cancellation tombstones) andstructured-agent-session-launch-reload.ts. - Provisional launch —
structured-agent-session-provisional-tab.tsandbeginStructuredAgentSessionProvisionalLaunchopen the chat tab and expose the durable sessionId before host acquisition settles;NativeChatLaunchRetry/NativeChatDeliveryRetryanduseNativeChatProvisionalLaunchdrive retry andtransportEnabled. - Reworked structured-chat tab close —
closeUnifiedTabremoves the local row synchronously and delegates host retirement toretireStructuredAgentSessionTab, with tombstone suppression wired into the snapshot/mirror path and silent cancellation during bulk worktree purges. - Gated provider transport on launch lifecycle — new
use-structured-agent-session-transport,…-transport-state, and…-optionswithhold provider hold/read/mutate and the option surface while a launch is pending, failed, or visibility-unknown.
ℹ️ Nitpicks
useNativeChatProvisionalLaunchguards retry onworktreeIdsourced fromuseNativeChatFileLinkContext; when that context is null theNativeChatLaunchRetryaffordance still renders but the click is a no-op (src/renderer/src/components/native-chat/use-native-chat-provisional-launch.ts:11-16). Passing the worktree the pane overlay already holds would remove the dead end.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
| provisional: boolean | ||
| onError?: (error: unknown) => void | ||
| }): void { | ||
| if (args.provisional) { |
There was a problem hiding this comment.
The cancellation tombstone is written only for provisional closes, but this delta also moved local row removal ahead of host retirement (tabs-close-actions.ts removes synchronously; this function fires agentSession.close and session.tabs.close asynchronously). A published session has no tombstone, so a host frame that still contains it — an unrelated worktree update during the close round-trip, or any frame at all when the close RPC rejects — re-mirrors the row through buildMirroredAgentTabs. On main the close awaited the host calls and then removed the row, and a rejection surfaced a toast; now a failed retirement is swallowed as a console warning with no retry, so the resurrected row can persist.
Technical details
# Published structured tabs have no close tombstone
## Affected sites
- `src/renderer/src/runtime/structured-agent-session-tab-retirement.ts:66-70` — tombstone only when `args.provisional`; `retireStructuredAgentSessionTab` swallows rejections (no `onError` from `tabs-close-actions.ts`).
- `src/renderer/src/store/slices/tabs/tabs-close-actions.ts:51-71` — `provisional` is false for a published session (`shouldRetainStructuredAgentSessionLaunchTab` covers only pending/visibility-unknown/failed), and the row is removed later in the same synchronous `set`.
- `src/renderer/src/runtime/web-session-tabs-sync/terminal-surfaces.ts:64-69` and `apply-preparation-browser.ts:177-182` — the mirror/suppression path only consults the tombstone, never `withLocalSessionTabCloseOwner` (that owner is consumed by `session-tab-ipc-bridge.ts` only).
- `src/renderer/src/components/tab-group/workspace-tab-close-commands.ts` — the pre-`dc9c77c` branch awaited `closeStructuredAgentSession`/`session.tabs.close` before `closeUnifiedTab` and toasted via `reportStructuredSessionCloseError`; that ordering was removed.
## Required outcome
- Closing a published structured chat must not re-add its tab from a host frame that predates the host acknowledging the close, and a failed host retirement must be retried or reported rather than silently lost.
## Suggested approach (optional)
- Record the cancellation tombstone for every user-initiated structured tab close (not only provisional), or restore the local row only after the host close resolves; pass an `onError` that reports and/or retries the host close.| // Why: failed launches keep their draft/outbox, so a retry must not stage the same prompt twice. | ||
| const text = retrying ? '' : outboxPromptText(joined) | ||
| const stagedPrompt = text | ||
| ? enqueueStructuredAgentSessionLaunchPrompt(existing.intent.sessionId, text) | ||
| : null | ||
| if (!retrying) { | ||
| launchDraft.seedStructuredAgentLaunchDraft(existing.intent.sessionId, agent, joined) | ||
| } | ||
| const { prompt: _retryPrompt, ...joinedWithoutPrompt } = joined | ||
| const callerOptions = retrying ? joinedWithoutPrompt : joined |
There was a problem hiding this comment.
When an existing state is failed or visibility-unknown, retrying strips prompt for every joining caller, not just a re-stage of the same prompt. A second launch for the same agent:worktreeId identity that carries a different prompt therefore has it neither staged in the outbox nor seeded into the composer, and settleStructuredAgentLaunchPrompt returns undefined — so no promptDeliveryResult is reported and the retained outbox entry delivers the earlier prompt in its place.
Technical details
# Retried launch drops a joining caller's prompt
## Affected sites
- `src/renderer/src/lib/structured-agent-session-launch.ts:181-190` — `text = retrying ? '' : outboxPromptText(joined)` plus `callerOptions = retrying ? joinedWithoutPrompt : joined` applies to any joiner, while the comment only justifies skipping a duplicate re-stage (`failed launches keep their draft/outbox`).
- `src/renderer/src/lib/structured-agent-session-launch-prompt.ts:97` — with no `prompt`, `settleStructuredAgentLaunchPrompt` returns `undefined`, so the caller's `promptDeliveryResult` is absent.
- Impact: `runSourceControlAgentActionStart` treats a missing `promptDeliveryResult` as success and fires `onLaunched`, so irreversible follow-up runs as if the prompt reached the provider.
## Required outcome
- A joiner with a prompt distinct from the retained entry must have it staged/delivered (and report a delivery result); only a re-staged duplicate may be skipped.
## Suggested approach (optional)
- Compare the joining prompt against the retained draft/outbox entry (or carry an explicit `reuseStagedPrompt` flag) and strip the prompt only on a match; stage it normally otherwise.
PR #20502 Electron validation — native structured-chat launch visibilityScenarioHidden Electron validation of Launched a structured Claude chat from the real New tab → Claude menu. Sent a composer message after the ordinary chat/composer appeared. Launched a structured Codex chat from No production code was edited. Launch-policy was rebuilt by the electron-vite dev runner (main+preload) before the app started. Identity
Visible evidenceClaude (New tab menu, immediately after click):
Claude after send (same tab/session id):
Codex (same-turn launch handle):
Backing store evidence
ScreenshotsUncropped 1728×994 hidden-renderer CDP captures: Local paths (not committed): CleanupPlaywright session Remaining gaps
|
|
Review complete on
The current PR matrix is green for typecheck, test shards, package (macOS and Windows), changed E2E build/specs, and wire compatibility. The remaining red |
There was a problem hiding this comment.
ℹ️ No new issues found in this commit. The two findings from the previous review remain open and unchanged by this delta, so approval is withheld.
Reviewed changes
Reviewed the incremental delta 5d8692db1c..f9054bc564 — commit f9054bc564 ("coordinate launch prompt settlement", 13 files).
- Shared launch-prompt dispatch — new
shareStructuredAgentLaunchPromptDispatch/getStructuredAgentLaunchPromptDispatchkeyed bysessionId:clientMessageId:fence, so the provisional-chat outbox hook and the launch settlement issue oneagentSession.sendfor the same staged entry; launch entries gainsource: 'launch'and a fence-agnostic prefix lookup lets a hook that mounts before the fence joins the in-flight admission. - Outbox dispatch extraction — the RPC/disposition path moved to
structured-agent-session-outbox-dispatch.ts;readOutboxgainedrecoverDispatching, and the mounted reader demotes an interrupteddispatchingentry tounconfirmedonly when no launch dispatch is in flight, so mirror reads no longer mutate state. - Cancellation retirement fencing — new
structured-agent-session-launch-cancellation.tsarms a tombstone's retirement atauthoritativeInventorySequence + 1when the cancelled launch settles;refreshLocalStructuredSessionTabsallocates the authoritative-inventory sequence before IPC and threads it throughStructuredSessionSnapshotApplyOptions, so a reply that began before settlement can no longer retire the fence. - Status hook extraction —
useStructuredAgentLaunchStatusmoved tostructured-agent-session-launch-status.ts(re-exported fromstructured-agent-session-launch.ts) to keep the registry under the max-lines cap.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
|
Closing readiness pass completed at
|
There was a problem hiding this comment.
✅ No new issues found in the two follow-up commits.
Reviewed changes
Reviewed the incremental delta f9054bc564..1224a61f49 — commits b25c9d3b3e ("align unified close ownership") and 1224a61f49 ("correct lifecycle imports and test typing"), 6 files.
- Fixed a broken lifecycle import —
snapshot-apply.tsnow importsbeginStructuredAgentSessionAuthoritativeInventoryfromstructured-agent-session-launch-cancellationinstead of re-importing it from the registry, which never re-exported it. - Aligned close tests with the unified owner —
useTabGroupWorkspaceModel.focus.test.ts,terminal-workspace-keydown.test.ts, anduseTabGroupTabCloseCommands.structured-session.test.tsnow assert the close routes throughcloseUnifiedTaband thatcloseStructuredAgentSession/session.tabs.close/cancelStructuredAgentLaunchare not called directly. - Test typo fix — declared
initialPropsexplicitly inuse-structured-agent-session-outbox.test.tsxto give thefenceprop a stablenumber | nulltype. - i18n copy — added the
worktree.creation.flow.structured.launch.unknownstring and theNativeChatLaunchRetrystrings (failed/unknown/retry) toen-runtime-required.jsonanden.json; no duplicate keys.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No critical issues — one reachability note inline.
Reviewed changes
Incremental delta 1224a61f49..c516c10a10 — commit c516c10a10 ("fence restored launch cancellations", 4 files).
- Restored cancellation fences —
restoreCancellationRetirementFencesseeds arestored/retireAfterInventory: nullentry for every persisted tombstone the process has not already seen, so a tombstone loaded after a reload is no longer retired by the next authoritative census. - Host cleanup before retirement —
claimStructuredAgentLaunchCancellationCleanupsclaims each restored tombstone once,refreshLocalStructuredSessionTabsissues a best-effortagentSession.close, andsettleStructuredAgentLaunchCancellationCleanuparms the fence atauthoritativeInventorySequence + 1on success (or resetscleanupStartedfor retry on failure). - Tombstone id export —
readStructuredAgentLaunchCancellationTombstoneSessionIdsexposes the persisted ids to the fence restore. - Test — a restored cancellation is drained across an authoritative then an ordinary inventory: the tombstone is retained until the close settles, exactly one
agentSession.closeis issued, and it retires on the following census.
The two inline findings from the prior review (structured-agent-session-launch.ts:181-190, structured-agent-session-tab-retirement.ts:66) are unchanged by this delta, so approval remains withheld.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
| const result = response.result as { snapshots?: RuntimeMobileSessionTabsResult[] } | ||
| const result = isStructuredSessionInventoryResponse(response.result) ? response.result : {} | ||
| const snapshots = result.snapshots ?? [] | ||
| if (options.authoritative === true || result.authoritative === true) { |
There was a problem hiding this comment.
result.authoritative can only be true for a client that advertises SESSION_TABS_AUTHORITATIVE_INVENTORY_RUNTIME_CAPABILITY, and the desktop renderer's fixed capability list omits it (src/main/ipc/runtime.ts:86-93). On native this branch therefore never fires, so a restored tombstone's host cleanup runs only when launch recovery or the retired-epoch repair lane issues an explicit authoritative: true refresh — not on the non-authoritative startup restore. Fail-closed either way (the tombstone keeps suppressing), so this is a deferred host-child close rather than a correctness gap; flagging it in case the intent was to drain restored tombstones at startup.
Technical details
# Restored-cancellation cleanup is unreachable on the desktop startup path
## Affected sites
- `src/renderer/src/runtime/local-structured-session-tabs-sync/inventory-refresh.ts:82` — the `result.authoritative === true` cleanup gate.
- `src/main/ipc/runtime.ts:86-93` (and `:138-145`) — the desktop renderer's `clientCapabilities` list, which omits `SESSION_TABS_AUTHORITATIVE_INVENTORY_RUNTIME_CAPABILITY`.
- `src/main/runtime/rpc/methods/session-tabs-inventory.ts:20-26,58-61` — `projectInventory` strips `authoritative` unless the client advertised the capability.
- `src/renderer/src/runtime/local-structured-session-tabs-sync/inventory-refresh.ts:45-57` — `restoreLocalStructuredSessionTabsOnce` calls `refreshLocalStructuredSessionTabs(expectedGeneration)` with no `options`, so the pre-IPC cleanup also does not run at startup.
## Required outcome
- Confirm whether restored tombstones are intended to be drained on the native startup restore or only by the explicit-authoritative repair/recovery lanes. If the former, the startup restore needs to trigger the cleanup (e.g. pass `authoritative: true`, or invoke `startRestoredLaunchCancellationCleanup` for a labeled census in the `startLocalStructuredSessionTabsSync` path); if the latter, a brief comment noting the desktop label gap would prevent a future reader assuming startup drains them.
## Open questions for the human (optional)
- Is the deferred host-child close on native acceptable, given the tombstone suppresses the row until a later authoritative refresh runs?…#20502) * fix(native-chat): bound structured chat launch * Fix post-merge test hygiene * Make structured fallback settlement exhaustive * fix(native-chat): release sessions after root exit * chore(i18n): remove legacy fallback copy * test(native-chat): remove terminal fallback census * docs(native-chat): clarify root-exit lease proof * chore(native-chat): drop unrelated formatting * fix native chat launch visibility * test(native-chat): split message rail windowing coverage * fix(native-chat): keep transport gating render-pure * fix(native-chat): coordinate launch prompt settlement * test(native-chat): align unified close ownership * fix(native-chat): correct lifecycle imports and test typing * fix(native-chat): fence restored launch cancellations * fix(native-chat): fence authoritative cancellation snapshots
* feat(work-items): port structured Start to Orca 1.4.203 Work Item Start opens exactly one structured agent session for a linked work item, delivers its prompt once, and refuses before creating anything when it cannot. A squash-port of the proven 1.4.201 checkpoint (05ac9d8) onto the official 1.4.203 base (tag v1.4.203, commit 776e424, tree df262ab). 1.4.203 ships no Work Item Start surface, so the whole contract still applies; the substrate it lands on changed in eight files and the delta was re-decided there instead of replayed. Contract (unchanged from the checkpoint) - `agent-session.work-item-start.v1` is a negotiated runtime capability; an older host does not advertise it and the route refuses instead of half-opening a session. - `launchOrigin: 'work-item-start'` admits one launch to the structured route and `launchAuthority` records who admitted it; both are host-derived, never wire-accepted. - Strict (`submit-after-ready`) launches declare no `legacyFallback`: a definitive refusal settles as `failed` and no terminal writer opens behind it. - The strict preflight runs BEFORE `git worktree add` and before `createFolderWorkspace`. - A structured session is created on the runtime that owns its workspace; an ambiguous owner fails closed. - `worktree ps` projects the structured `sessionId` and provider handle for Core adoption. - Packaged builds embed version/commit/tree/buildId; `status.get` publishes it and `runtime.buildAttestation` hashes the running executable on demand. Re-decided on 1.4.203 - Unknown send refusal: 1.4.203 (stablyai#20133) retains the operation id and replays the same envelope through the host's durable send plan, and names parking the entry as the head-of-queue wedge stablyai#19863 removed. The checkpoint's `unconfirmed`/-1 parking hunk in `requeueStructuredAgentSessionSendRefusal` is dropped; the official function is kept verbatim. The Start path still reports `deliveryUnknown` from the refusal code so no second writer is opened, and its test now asserts the official same-envelope replay (`retryUnknown` is compatibility-only on 1.4.203 and is no longer sent). - RPC params type parity (new in 1.4.203) required `runtime.buildAttestation` in the generated catalog; regenerated with the official script. - Mobile Tasks parity hashes recomputed over the merged 1.4.203 source. - Localization catalog: the Start strings now have `en.json` entries, which the 1.4.201 checkpoint lacked; `verify:localization-*` passes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Re-decided on 1.4.209 (port of this commit onto stablyai/orca v1.4.209) - 1.4.209 (stablyai#20502) removed the renderer terminal fallback and made structured launches open a provisional chat tab that owns its own retry. Strict Start no longer needs to withhold a `legacyFallback`: none exists. Strict worktree/direct/folder Starts now await the launch settlement and prompt delivery on top of the provisional handle; non-strict launches keep the upstream fire-and-forget behaviour. - The pending-creation recovery lane (`worktree-creation-structured-recovery.ts`) that 1.4.209 deleted is kept for strict Start only; its retry re-enters the launch registry, which restarts an unknown launch under the same intent instead of minting another. - The shared launch-prompt dispatch (stablyai#20502) returns a boolean; `deliveryUnknown` is carried beside it, and read back from the outbox entry when the mounted chat ran the shared send. - stablyai#20944 stopped routing on terminal arguments (`requiresTuiLaunchCommand`); the strict preflight and planners follow it, so they predict the same route as the post-create plan. - `launchOrigin` and the owning runtime target now survive the intent retry/restore split and the reload-persisted launch record (the create fingerprint covers `launchOrigin`). - Mobile: the createSupport probe uses the typed RPC operation; the new-worktree route builder carries the combined setup + session warning. * fix(work-items): scope every status event per subscriber; certify against 1.4.203 Two blockers the independent review found on 4e098c4, each with a regression that fails on the previous code. Status feed access scope (P1) - `subscribe(includeSession)` filtered only the opening snapshot; `broadcast` looked for a top-level `sessionId`, which a `status` event never has (it names its session at `event.session.sessionId`). A paired runtime denied a Work Item Start session therefore received every later publish and ownership revoke for it. - Every session-bearing event now passes the subscriber's filter (`scopeStatusEvent`): `status` is withheld, `snapshot` is filtered, `end` always arrives. The filter is dropped together with its subscriber on direct `unsubscribe`, on the disposer, and on emit failure; a re-subscribe under the same id never inherits an earlier filter. - Regressions: the feed itself (denied subscriber gets an empty snapshot and zero status events across publish and revoke, the allowed session still flows, `end` exactly once, filter cleanup on both removal paths) and the RPC boundary through `agentSession.subscribeStatus` with `canAccessWorkItemStartStructuredSession`, for a paired runtime and a mobile client on their own connections while the owner's connection receives both events. The RPC fixture now shares one real feed per test over a journal the test can advance, and exposes the live reply list of a streaming call. Certification baseline - `WORK_ITEM_START_E2E_BASELINE_VERSION` was still `1.4.201`, so this candidate's own evidence validator would have rejected a correct 1.4.203 paired run. It is `1.4.203` now, fixed rather than derived from the manifest: the manifest says what was packaged, the constant says what the owner certifies, and a self-consistent 1.4.201 pair must still be refused. Fixtures bind to the official v1.4.203 base (776e424 / df262ab, buildId 7bbdd813f784, as `build-provenance.mjs` derives it). Matched 1.4.199 and 1.4.201 pairs are rejected on both sides even when they match their own manifest; a 1.4.201 side behind a 1.4.203 side is rejected by baseline, identity, and version mismatch. The Windows-client, distinct-artifact, commit/tree/buildId, capability, single-executor, one-prompt, and zero-terminal checks are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(work-items): close the remaining 4e098c4 review findings (A–H) Each finding has a regression that fails on 8b87c61 and passes here; each was proved by reverting only that finding's implementation. A Strict quick-create keeps `launchOrigin: 'work-item-start'` and declares no `legacyFallback`; a definitive refusal settles as `structured-refused` (workspace kept, no writer, no terminal, Retry allowed). B Every launch exposes `recovery = { intent, clientMessageId }`. The pending creation persists it on an unknown launch/delivery and Retry re-enters with `recover`: the exact intent (same session id and create envelope, replayed through `reconcileUnknownLaunch`) and the staged prompt looked up by operation id. A lost staged entry is unknown, never success. Prompt staging moved to `structured-agent-session-launch-staging.ts`. C A definitively refused launch prompt is deleted from the outbox instead of requeued under a fresh id; only unknown / not-yet-admitted refusals retain and replay the same id. D Mobile admission is tri-state (`terminal` / `structured` / `refused` / `unknown`) with a 10 s status timeout; both Start entry points stop BEFORE `worktree.create` on refused or unknown and never fall back to the terminal. E The mobile send envelope is persisted in the operation journal before dispatch; `agent_session_operation_unknown` on `agentSession.send` is unknown; unknown outcomes replay the same operation id a bounded number of times and stay persisted until settled. F `prepareStructuredAgentSessionCreateForWorktree` passes `expectedWorktreeTarget` to the resolver, where the full record comparison (path, instance, identity, host, creator) runs. G `worktree ps` merges the host's projection with the hook-store row that shares its pane key: one row per session, with `sessionId` and provider handle. H Build provenance is fail-closed: any working-tree change yields no identity, an `ORCA_BUILD_COMMIT/TREE` that contradicts (or cannot be confirmed by) the repository throws, the packaged `app.asar` bundle is verified in `afterPack`, the candidate manifest is written after every artifact, `build:*` run `verify:build-provenance`, and the paired spec requires `ORCA_CANDIDATE_MANIFEST`. `ORCA_BUILD_UNCERTIFIED=1` is the only escape and embeds nothing certifiable. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Re-decided on 1.4.209 - B lands on the 1.4.209 launch registry: a recovery re-enters the registered state when it still exists (a restart that finds, rather than skips, the staged operation) and otherwise rebuilds it from the persisted intent through `reconcileUnknownLaunch`. Publication is verified with the authoritative tab snapshot (stablyai#20502 removed the renderer-tab shortcut). - A: 1.4.209 has no terminal fallback, so a refused strict create simply settles `failed` and maps to `structured-refused`. - `recovery` is optional on hand-built settlements; the settle loop always sets it. - D: the mobile admission read goes through a typed `status.get` RpcOperation, as the 1.4.209 raw-request-port ratchet requires. * fix(build): do not count electron-vite's transient config bundle as a dirty build input The first fail-closed package refused itself: electron-vite bundles its config into `electron.vite.config.<timestamp>.mjs` in the working directory while the config loads — which is when `readBuildProvenanceLiteral` runs — and deletes it afterwards. It is derived from the tracked config, not an input. Ignored in git and excluded by exact pattern in `dirtyBuildInputs`; a modified config or any other stray file still yields no identity. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(work-items): strict quick-create never reads missing delivery evidence as success Re-review counterexample on e7567c6: a structured settlement that resolves with a session but carries no prompt-delivery state completed the strict creation, because `!delivery || delivery.delivered` took the success branch. Missing evidence is not evidence of delivery. In strict mode an absent delivery result now settles as `promptDeliveryUnknown` with the launch's `recovery`, so Retry reconciles the same session and operation; nothing completes, nothing mints a second session, and no terminal fallback exists on this path. Regression: the counterexample itself, asserting durable unknown with the recovery intent, one launch, and only the structured chat's own activation (never a startup terminal). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(build): bind provenance to the real embed site and keep the transient bundle out by timing Two re-review holes in the provenance gate. P1 — a name-based whitelist admitted ANY `electron.vite.config.<digits>.mjs` as clean, so an unverifiable file could hide under a numeric transient name. No name is exempt now: the build wrapper (`run-electron-vite-build.mjs`) reads the identity BEFORE electron-vite writes its transient config bundle and hands it to the config through `ORCA_ELECTRON_VITE_BUILD_PROVENANCE`; the config accepts it only if its commit and tree are what git reports at load time (malformed, contradicted or unconfirmable hand-overs throw), and falls back to the strict read without it. The gitignore entry is gone, so a lookalike is a dirty input like any other and the identity is null. Regression: the exact counterexample (`?? electron.vite.config.123456789.mjs` → dirty → null) beside the genuine path (hand-over accepted while the transient is on disk). P2 — the bundle verifier accepted any bundle in which the expected strings occurred somewhere, so `value:null` plus a comment passed. The runtime read site now carries an exact sentinel (`site:'orca:build-provenance:embed', value:<substituted>`, checked at runtime so no minifier drops it) and the verifier parses the value right after that sentinel: exactly one site, a well-formed identity, equal to the expected one; null, an unreplaced identifier, a malformed object, two sites, or another build's identity all refuse, whatever else the bundle contains. Regressions: null + expected strings in a comment; forged identity at the site + expected strings elsewhere; duplicate sites; unreplaced identifier; sentinel byte-equal on both sides. Packaging hooks: `artifactBuildCompleted` validates the AppImage synchronously again and returns the manifest write; `ORCA_BUILD_UNCERTIFIED` is read per call. The electron-builder hook tests broken since b52da80 are fixed (layout tests run as throwaway packages) and two deterministic refusals added: a manifest under a contradicted override, and an afterPack whose packaged bundle embeds null. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(work-items): settle mobile M1 only on ledger settlement, hold the workspace lifecycle through attach, full sha for mac local builds Final source round of the independent review (R1–R3), each with a regression that fails on the previous implementation. R1 — mobile durable journal. `deliverWorkItemStartPrompt` cleared the persisted M1 for every non-unknown mutation result, so a lost reply followed by an `agent_session_operation_capacity` ("try later") replay deleted M1 and reported undelivered while the host may hold it. The canonical `agentSessionRefusalOperationState` now decides: `unknown` and `pending-admission` (and any code the ledger does not classify) replay the SAME operation id a bounded number of times and, still undecided, stay persisted and are reported `unconfirmed` with the exact envelope; only `settled-rejected` (and a pre-handler failure that never reached the host) clears. M2 is never minted. R2 — worktree target TOCTOU. The full expected-target comparison ran in the resolver, then launch preparation awaited, then `attach` ran with nothing keeping the record what it was. The runtime now has a workspace lifecycle hold (a second instance of the existing shared/exclusive `WorktreeTerminalMutationLock`, its own key space): a structured create holds the shared side from authoritative resolution through attach — scoped creates before resolving, generic ones once resolved — and `removeManagedWorktree` (every removal branch, therefore every replacement) takes the exclusive side around the whole removal, refusing as `worktree_lifecycle_busy` past a bounded wait rather than being granted later. Belt to that brace: `commit` re-resolves the exact full target (path, instance, identity, host, creator) under the hold immediately before attach and refuses on any difference. Deterministic regressions with the real `OrcaRuntimeService`: A attaches once; A replaced by B during a paused preparation → attach never called; a removal waits for the in-flight create; a create arriving during a removal sees B and is refused; a removal past its deadline is busy. The terminal-only lock is not used: removal never took it. R3 — mac local build. `getLocalBuildIdentity` passed `git rev-parse --short=12` as `ORCA_BUILD_COMMIT`, which the provenance gate compares byte-for-byte with `rev-parse HEAD`, so the local mac build refused itself. It now hands over the full sha (and refuses anything else); only `createLocalBuildVersion` abbreviates it for the human version suffix. The verifier is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(work-items): strict quick-create fails closed on a thrown launch or a generic failed settlement Last material blocker of the independent review on 0fd77da: with a strict (`submit-after-ready`) non-empty prompt, `launchStructuredWorktreeSession` still answered `accepted: true` when the launch threw during launch-intent creation (an ambiguous runtime owner) or when the settlement was a generic `{ kind: 'failed' }`; the caller then completed and removed the pending creation with no structured session and no delivery proof. Both now settle as `failure: 'structured-launch'` in strict mode — the same discipline as a host refusal: the workspace exists with no writer, no legacy terminal opens, the creation is marked as a retryable error instead of completed, and nothing mints a second session. The non-strict path is unchanged. Regressions (the strict suite moved to `worktree-creation-structured-session-strict.test.ts` to stay under max-lines): a thrown `worktree_runtime_owner_ambiguous` from the launch's sync prologue, and a generic failed settlement, each asserting `accepted: false`, `failure: 'structured-launch'`, no fallback claim, no startup terminal, no after-wake terminal, no trust preflight; and, at the flow level, that such a result is kept on the retry surface and never completes or removes the pending creation. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Re-decided on 1.4.209: the strict lane also fails closed when the provisional chat surface could not open (reveal failed) and the creation was not cancelled; the strict suite runs the real plan, settle loop and provisional tab, and asserts the only activation is the chat's own. * fix(work-items): strict quick-create fails closed when the launcher itself throws The same fail-open one level above e1d9c5e's fix: the caller of `launchStructuredWorktreeSession` in `worktree-creation-flow-execute.ts` caught any exception that escaped the launcher, logged it, and fell through to `completeWorktreeCreation` with `structuredLaunchAccepted` still true — no session, no delivery proof, pending creation removed. For a strict (`submit-after-ready`) Work Item Start an escaped exception now marks the existing workspace with the retryable `structured-launch` error and returns before completion: no legacy terminal, no pending removal, no second session. The non-strict path keeps its historical behaviour (treated as a failed-but-accepted launch). Regression at the caller level: `launchStructuredWorktreeSession` itself is mocked to reject (not the plan inside it); `completeWorktreeCreation` never runs, the pending entry is a retryable `structured-launch` error bound to the worktree, and no startup / activation terminal is seeded. Fails on e1d9c5e's file, passes here; the helper-level throw and generic failed-settlement tests are kept. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * chore(work-items): clear the changed-code quality gate (83 type-assertion findings) without behaviour change `check:code-quality:changed` against the official v1.4.203 base reported 83 `typescript/consistent-type-assertions` findings across the port (9 production sites, 66 in tests). It now passes with 0 findings. Production: checked narrowing instead of casts. A tiny shared guard (`isUnknownRecord`) narrows untyped input in the build-provenance parser, the launch-authority parser and the mobile admission reader; the attestation stream is typed as an `AsyncIterable<Buffer | string>`; the pre-create preflight declares the detected-agents promise type the routing already requires; the session-tab projection spreads the base projection over the change instead of asserting; the IPC dispatch response needed no assertion at all. One production cast is unavoidable and carries the repository's line-specific SAFETY rationale: the `globalThis.ORCA_BUILD_PROVENANCE` read, which the packager's define substitutes textually and which the cli/mobile tsconfigs (no `build-constants.d.ts`) also compile. The inherited web settings API `satisfies … as` carries one too (the web preload implements a checked subset). Tests: `vi.hoisted` state is declared with types instead of asserted; RPC/mock params are read through `isUnknownRecord`; `JSON.parse` results are assigned to typed bindings; DOM elements are narrowed with `instanceof`; worktree-target fixtures are typed with `Pick<Worktree, …>`. Fixtures that deliberately pin a subset of a large interface (runtime stores, structured hosts, protected resolvers, GlobalSettings) keep a single SAFETY-annotated cast each, usually behind a small helper so one rationale covers the repeats. No rule is disabled and no allowlist grows. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * chore(work-items): recompute Mobile Tasks parity hashes over the merged source The refactor-parity guard pins hashes of the Mobile Tasks source; the port onto v1.4.205 merges the Work Item Start changes into upstream's RpcOperation migration of the same files, so the screen-hook, statement, declaration and semantics fingerprints (and the semantics line count, 3452 -> 3458) are recomputed over the merged tree, exactly as the 1.4.203 port did. Render tree, style and diff-hook fingerprints are unchanged. No behaviour change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Re-decided on 1.4.209: recomputed over the Work Item Start changes merged into the 1.4.209 Mobile Tasks source (semantics line count 3274 -> 3280); render tree, style and diff-hook fingerprints unchanged. * fix(work-items): scope the workspace lifecycle hold per execution host on 1.4.205 Upstream v1.4.205 runs same-id workspace removals on different hosts concurrently (a local row and an `ssh:*` row under the migrated spelling), and its integration suites build partial runtime fakes. The reviewed R2 hold (0fd77da) keyed the lifecycle lock by worktree id alone, so on the 1.4.205 base the remote removal waited on the local one until `worktree_lifecycle_busy` and three upstream integration suites failed with `holdWorktreeLifecycle is not a function`. - `runtimeWorktreeLifecycleKey` keys the hold per execution host; no host is the local machine, which is what an unscoped hold always meant. - The create path holds the host the admitted target names (or the resolved location's host); the removal path holds the host it routes to, resolved from the explicit host or the owner repo before the hold is taken. - The removal hold helper receives the scope it needs instead of a bare id. - Upstream's three integration fixtures declare the hold, as the reviewed fixtures already did. - Regression: a remote exclusive hold neither waits for nor blocks the local create, and the local scope is still the one the create held. Verified on this tree: `pnpm tc`; the previously failing suites plus their neighbours (orca-runtime, orca-runtime-tests, structured-session integration/replay/claude, create-lifecycle, precommit refusal, worker seed options, worktree ps: 8 files, 1320 tests) pass; oxlint on changed files; `check:code-quality:changed` against 11aba8b: 0 findings; `git diff --check`. The one remaining failure in the targeted desktop run, `hourly-build-version.test.mjs` "stays on the already-shipped hourly base", fails identically on pristine v1.4.205 (upstream's own stale expectation) and is not part of this delta. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: repair v1.4.205 port CI and Windows provenance paths Re-decided on 1.4.209: the legacy adapter freeze hunk is dropped — 1.4.209 (stablyai#20872) already reads own-property descriptors, which is the lint fix this worked around; the added freeze assertions are kept and pass. The mobile admission read keeps its typed operation. * test: check Windows target watcher in architecture packaging guard * test: use retrying cleanup for Windows package fixtures * fix(work-items): preserve scoped Start authority at production callers * fix(work-items): retain strict delivery across incomplete settings refreshes * fix(work-items): preserve legacy settings wire behavior outside strict Start * fix(codex): preserve yolo semantics in structured app-server Re-decided on 1.4.209: stablyai#20944 deleted `codex-structured-app-server-args.ts` and stopped passing configured CLI arguments to app-server. The Yolo posture now travels as the thread's explicit `approvalPolicy: never` / `sandbox: danger-full-access` on thread/start and thread/resume, derived from the bypass flag wherever it sits in the arguments, and conflicting `-c approval_policy` / `--sandbox` tokens never reach app-server. So the patch's `-c` overrides are dropped with the deleted file, and its guarantee (Yolo is final regardless of argument order) is pinned against `codexStructuredPermissionPolicyForSettings`. * fix(mobile): restore the 1.4.209 web client context seam The 1.4.205 CI repair moved the RPC client context into a native-only rpc-client-context.ts and pointed host-client-hooks at it. 1.4.209 ships a client-context.web.tsx with its own context, so on the web page every useHostClient read a context the web provider never fills: 27 bridge-parity goldens failed (base: 793/793). Back to upstream's import layout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(work-items): adapt direct and composer Start to 1.4.209 Split the strict submit-after-ready half of launchWorkItemDirect into launch-work-item-direct-strict-start.ts and the mobile composer Start into composer-work-item-start.ts, with unchanged behaviour. The strict pre-create preflight keeps reading the settings snapshot taken at launch rather than re-reading the store after the awaits. 1.4.209 keeps structured launches with configured agent arguments and refuses only a command override, so the unavailable copy now says "custom launch command" and the folder composer test pins arguments as non-blocking. Test fixtures follow 1.4.209 APIs (retired-name registry, agentLaunchSupported, repoName, provisional-tab store fields, cancelStructuredAgentLaunch). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mobile): read a missing support method as a verdict The Start's probeFailed marks an unanswered createSupport probe so a strict Start reports unconfirmed instead of refused. It also swallowed method_not_found, which is a host answering that it has no structured route: structured-launch-support-refused diverged from its golden. Definitive codes (isDefinitiveAgentSessionCreateRefusal) are plain unsupported again; other ok:false replies stay probeFailed, so the two reply-matrix goldens whose generic refusal partitions now settle probeFailed are re-recorded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(work-items): pin the closures and capabilities Start changes on 1.4.209 - The structured RPC module imported the native-chat write floor from mobile-native-chat-send, dragging terminal-write families into the Tasks and Files pages. The constant moves to a leaf module. - The layout's composer now opens the Start's structured session, so C1 (and C5, which inlines it) pins agentSession.structured-create/launch with their replay verdicts; C2/C3/C5 censuses move by +2 families, +9 goldens. - The session route closure is 4329 modules (+6 Start modules). - The desktop renderer advertises the structured capabilities to paired remotes too, which the port added so a paired Start is not refused; the divergence test records that decision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(daemon): reap terminal descendants during shutdown on 1.4.209 Port of upstream 632ae13 ("fix(daemon): reap terminal descendants during shutdown (stablyai#22232)") onto the v1.4.209 tree. The release's pr.yml already runs config/scripts/run-daemon-shutdown-descendants-docker.mjs, but the harness was never shipped and the leak it guards was live: a setsid'd, SIGTERM-ignoring child of a live PTY shell survived daemon exit. Host dispose now captures each live session's descendants, escalates them with identity-checked SIGTERM/SIGKILL and waits for verification (bounded to 2.5s inside the daemon's 5s budget) before force-killing the shell and releasing its PTY. In-flight teardowns are upgraded to immediate and awaited instead of being disposed a second time. Carried as-is: the teardown/verification runtime, the per-pid partial read verifier, the process-table parser split, tests, and the Docker oracle plus its PR-scope classification. Not carried: the daemon-cgroup-scope busctl hunk (the module was removed in 1.4.209) and the pr.yml step (already present in the release). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(work-items): scope Start session reach to its owner on 1.4.209 - hold, reveal, restartResumable/Resume/Continue authorize each concrete sessionId: a Work Item Start session is reachable by its owner alone, even with global structured chat off; cross-owner access is refused. - Add the Start-only client capability agent-session.work-item-start.client.v1. Paired desktop and web clients advertise it instead of the generic structured-session capability, so Start no longer grants reach into ordinary structured chats. Hosts still accept the generic capability. - Ordinary structured tab restore stays behind the v1.4.209 setting; with it off, a Start caller restores only its own visible Start tabs, latched per record so owners never spend each other's sweep. - worktree.ps drops sessionId and provider thread ids from structured rows the caller cannot reach. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(work-items): close the remaining 1.4.209 Start review findings Settings wire: settings.update accepts the optional workItemStartPromptDelivery ('draft' | 'submit-after-ready') that paired web clients already send; legacy payloads are unchanged and an unknown value is refused. Prompt refusal: only a strict Work Item Start (submit-after-ready) settles a definitively refused prompt, and it names one fresh retry id. Every other structured launch keeps the v1.4.209 requeue. Desktop strict retry: a recovery whose staged prompt is gone restages it under the SAME operation id, so the host ledger replays an accepted send instead of delivering twice. A strict refusal leaves one retry id and keeps Retry enabled. A refused create retries with its never-dispatched prompt, and a thrown unadmitted refusal is definitive rather than unknown. Quick create: the strict preflight checks the host the workspace runs on (ephemeral VM or run target), not the source repo's, before any worktree exists. Mobile: a strict Start refuses SSH, runtime-hosted and WSL repos before worktree.create. A durable attempt journal, written after worktree.create and before the session request, makes every retry re-enter the same session id and create operation. The composer returns the created workspace with a warning instead of an error that left Create armed for a second workspace; the workspace screen offers Retry Start; the Tasks create path has a synchronous in-flight guard. Support probes are bounded to 10 s on mobile and desktop. Build provenance: the hourly, daily and adhoc mac version scripts emit the full commit sha (display names still slice seven), and provenance refuses any ORCA_BUILD_COMMIT/ORCA_BUILD_TREE that is not a full 40-hex id. Certification evidence: the 1.4.203 pin is gone and evidence binds to the candidate manifest's version, commit, tree and build id, with an optional ORCA_CANDIDATE_EXPECTED_COMMIT pin. The runtime attestation adds optional appContent (the app.asar hash) and attestationId, and packaging records the asar hash per artifact in the manifest. The paired spec runs its contract steps in PR CI against a new inert Codex app-server fixture; candidate evidence runs only with ORCA_WORK_ITEM_START_CERTIFICATION=1, where nothing may skip. Also: the terminal-paste path is fenced from strict Start and pinned by a test, mobile/pnpm-lock.yaml is restored to its v1.4.209 bytes (a format-only rewrite with no package.json change), and three parity pins plus six mobile RPC goldens are refreshed for the intended probe-timeout and create-guard changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(work-items): close the 1.4.209 Start CI findings outside the RPC pin - runtime-executable-attestation: read the original-fs shape and process.resourcesPath through typed property access, not Reflect.get. - write-candidate-manifest: rpm names x64 `x86_64`, which the bare `x86` match read as ia32; the package job refused the manifest for want of an ia32 app-content sidecar. Covered by a linux x64 binding test. - Session route closure 4329 -> 4333: the four Start modules added in 8ecea75 (attempt journal, prompt delivery, retry banner, execution host). - Web runtime capability tests assert the Start-scoped structured capability is advertised and the generic structured capabilities are not. - The e2e workflow contract finds its Windows launcher by listing rather than naming a batch-shim literal; it only reads the file, never spawns it. - en.json gains the retryable prompt-refusal copy the recovery path reads. The RPC recording pin stays red: the 9 structured goldens record the support probe's new 10s budget, which the pinned baseline tree does not send. Only a repin plus full re-record satisfies both replays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(rpc): repin recordings to final 1.4.209 Start candidate * fix(work-items): refuse unsupported strict Start repos before worktree.create A strict Work Item Start now asks the host for the source repo's structured-session verdict (agentSession.createSupport with a repo selector) after admission and before any setup or worktree.create. A remote/wsl/agent refusal, an older host that rejects the repo params, or an unanswered probe stops the Start with nothing created. Draft mode never sends the probe. The host derives the would-be location from the repo row, the project runtime's configured WSL distro and a WSL UNC path, so a C:\ repo whose project runs in WSL is refused before a workspace exists. Also binds mac zips to the macos app-content sidecar in the candidate manifest, refusing zips whose platform is ambiguous. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(mobile): expect the pre-create repo probe in the Start submit hook ef2ff81 made a strict Work Item Start ask agentSession.createSupport with a repo selector before worktree.create, and updated the Tasks-side tests, but not the new-workspace submit hook test. Its expected call sequence still omitted the repo probe, failing Mobile Checks deterministically. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(work-items): deliver strict quick-create Start as submit-after-ready Quick-create derived its structured prompt delivery as `auto-submit` whenever no draft prompt existed, which is always the case for a strict Work Item Start. The launch carried `launchOrigin: work-item-start` but not `submit-after-ready`, so the strict send guard never applied and a refused send was requeued into the legacy chat outbox behind the Start. The route resolver now owns the delivery decision: a strict Start always delivers `submit-after-ready`, legacy quick-create keeps `draft` / `auto-submit`. The worktree-creation launch boundary also pins strict delivery from `workItemStartPromptDelivery`, so a stale request cannot reach the legacy requeue. A strict Start with an empty prompt is refused before any workspace exists instead of completing or reconciling on an empty send. Direct and folder Start are unchanged; both already sent `submit-after-ready`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(work-items): report managed Claude account ineligibility as agent, not wsl `agentSession.createSupport` answered a managed Claude account mismatch with reason `wsl`, even for a native repo, so mobile told the user the repository runs inside WSL and orchestration downgraded with "this workspace runs under WSL". The verdict now uses the existing `agent` reason, which every client already maps, so no schema or wire change is needed. Mobile's `agent` copy, before and after create, now names the selected agent or its active account. Real WSL workspace / project-runtime refusals keep reason `wsl`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(work-items): add strict Start empty-prompt localization * fix(work-items): single-flight strict Start and Retry per workspace A mobile Start and a Retry Start (or two Retry taps) for the same workspace could overlap. The attempt and send journals only make a sequential re-entry safe: the first flow cleared the send record when it settled, the overlapping one minted a new send operation, and the prompt reached the session twice. Start and Retry now share one module-level in-flight promise per workspace. Every overlapping caller awaits that same promise, so there is one create, one session and one prompt. The slot is released only by the promise that holds it, on success or on a throw, so a later Retry after a settled outcome runs normally. Retry reads the attempt journal inside the flight, so a stale banner tap after a settled Start finds nothing and sends nothing. The banner also guards double taps with a ref. A phone paired with mobile scope is now told that the refusal is about its pairing scope instead of being blamed on an older host. Pairing authority is unchanged. The pre-create route decision moves unchanged to work-item-start-route.ts (plus its tests) to keep both files under max-lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(work-items): pin strict Start mobile route closure * chore(work-items): preserve reviewed mobile Start sources --------- Co-authored-by: salvadorgu7 <206022846+salvadorgu7@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>






ELI5
Opening a structured agent chat now gives the user a usable chat view immediately. Provider acquisition continues behind that view, drafts and sends remain durable, and a slow or refused structured launch never silently turns into a legacy terminal.
What Changed
agentSession.createspan with no branch, path, prompt, session content, or sensitive identifier attributes.Why
#19154 records a Claude root that exited before producing output. Because the descendant snapshot was still unverifiable, eviction treated the dead lease owner like a live descendant and could not release the lease or host session.
The durable lease identifies the provider root PID and start time, so first-hand root death is enough to release that root-owned lease. This does not claim that unknown descendants exited; later cleanup still preserves that uncertainty.
The user-facing fix is architectural: the chat surface is available immediately and acquisition is background work. A timeout or refusal therefore cannot replace the structured route with a different product surface.
Linked Issue
Fixes #19154
Testing
pnpm testargument.pnpm run check:code-quality:changed: passed with 0 findings across 103 changed files.git diff --check: passed; lockfile restored and clean.pnpm tc: reached TypeScript but failed on 73 pre-existing editor errors caused by mixed installedprosemirror-model1.25.4/1.25.11 trees. No reported error references the native-chat changes.Review
The branch was merged with current
origin/mainand pushed as5d8692db1c. The current head also structurally splits the upstream message-rail windowing coverage to satisfy the repository max-lines rule without a lint suppression and keeps transport gating render-pure. Earlier automated comments about a startup label and 30-second terminal fallback describe superseded behavior; the current design intentionally removes both. Please review the immediate provisional-tab lifecycle, root-owned lease release, and cancellation/publication fencing on this head.Notes