Skip to content

fix(server): settling a parent cancels its delegated child's armed usage-limit resume - #30175

Open
juliusmarminge wants to merge 7 commits into
mainfrom
fix/issue-30144
Open

juliusmarminge wants to merge 7 commits into
mainfrom
fix/issue-30144

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Problem

A delegated child that fails on a usage limit while its parent is open gets an auto-resume armed for its reset. If the parent is settled before the reset, the armed resume still runs the child, and the child's delegated-completion wake reopens the settled parent and starts a new run on it. https://git.cafe/pingdotgg/t3code/pulls/27951 only stopped new arms for children of settled parents; an already-armed resume still fired. Reproduced on main (cefb544) with the runtime test below, through the real orchestrator.

Change

The usage-limit continuation guard in Orchestrator.dispatchMessage now also refuses a resume when the thread is a subagent whose direct parent is settled. The parent row is read only for subagents, only on this path, and inside the child's serialized dispatch, so a parent settled after the sweep read it is covered.

When the refused command still matches the thread's armed recovery (same run and request id), the refusal retires it: autoResume: false with a fresh request id, the same state as "Cancel auto-resume". Reopening the parent later does not revive it, and the sweep has nothing left to offer for that thread. A stale resume whose request id no longer matches (the user re-armed in the meantime) takes the existing no-op path, so it never overwrites a newer choice. If the parent is reopened before the reset, the child resumes as before.

The user guide's usage-limit section now says a delegated task's continuation is cancelled if its parent is still settled when the reset arrives.

Scope and approval

Fixes #30144

Triaged via the automated issue flow.

Verification

Server-only change with no visible surface, so no captures. Proof is one regression in runtimeLayer.test.ts, "retires a usage-limited subagent resume once its parent settles", through the real orchestrator: child armed while the parent is open → parent settled → reset passes → the sweep's resume is dispatched. It asserts no new child run, the parent still settled, the recovery disarmed; that unsettling the parent afterwards yields no limit-resume: and no limit-arm:; that re-dispatching the same saved resume does not leave the child armed but stuck; and that a stale resume after an explicit re-arm leaves the user's recovery untouched.

Red with main's Orchestrator.ts (the child starts a second run):

FAIL  runtimeLayer.test.ts > usage-limit recovery > retires a usage-limited subagent resume once its parent settles
AssertionError: expected [ { …(16) }, { …(16) } ] to have a length of 1 but got 2

Green at this head:

vp test run runtimeLayer.test.ts -t "limit"                            Tests  21 passed | 92 skipped (113)
vp test run ProjectionStore.test.ts UsageLimitRecoveryWorker.test.ts   Tests  56 passed (56)
vp run check:changed                                                    passed (format, lint, knip, typecheck, tests)

Not checked: a live provider hitting a real usage limit through child completion and parent wake (no test environment can trigger a real Claude limit), and the reporter's macOS desktop build. The change is server-side and platform-independent. Delegation to a linked environment uses a different lineage and is not covered by this change (the same scope as https://git.cafe/pingdotgg/t3code/pulls/27951).

Claude Opus 5.5 (Claude Code in T3 Code), with reviews by GPT-6.1 Sol (Codex) and Claude Opus 5.5.

🤖 Generated with Claude Code

juliusmarminge and others added 6 commits November 3, 2026 12:18
…ttles

These tests reproduce #30144: a subagent child armed for usage-limit
recovery while its parent was open still yields a limit-resume: command
after the parent is settled. The final assertion of the #27951 test
encoded the bug; it now expects no resume while the parent is settled,
and a resume once the parent is unsettled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ttled

An auto-resume armed for a subagent while its parent was open still fired
after the parent was settled, and the child's completion reopened the parent.
The settled-parent guard in getLimitRecoveryCandidates only covered fresh
arming; it now gates the armed-resume branch too. Unsettling the parent makes
the armed resume eligible again.

Fixes #30144

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ttles

Gating the resume branch on the parent only paused it: unsettling the
parent later resumed the abandoned subagent at any age. The sweep now
disarms such a recovery (autoResume false for the same run and reset, as
Cancel auto-resume does) through a plain dispatch, so it stays disarmed
after the parent is unsettled. Fresh arming stays blocked in SQL as on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…arent settles

The usage-limit sweep can read a subagent's candidates while its parent is
open, and the parent can be settled before the saved limit-resume dispatch is
decided. The continuation guard checked only the child's own settled state, so
the child started and its completion wake reopened the settled parent.

The guard now also refuses when the thread is a subagent whose direct parent
is settled, taking the existing no-op path; the next sweep then sees
parentSettled and disarms the recovery.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous shape split the cancellation across the recovery sweep, the
worker and the orchestrator guard, which left two races: a skipped resume
kept the same requestId and replayed its receipt forever, and a disarm
prepared while the parent was settled could overwrite a newer explicit
re-arm.

Revert the store and worker to main and do the cancellation in the
serialized resume dispatch: when the parent is settled and the recovery
still matches the command, the no-op metadata update sets autoResume to
false with a fresh requestId. Stale commands keep the existing no-op.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gate

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Nov 3, 2026
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Nov 3, 2026
@macroscopeapp

macroscopeapp Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 0b048fa

Macroscope's review found this PR approvable — Cancels a delegated task’s scheduled usage-limit resume when its parent remains settled, preventing unwanted work from reopening the parent. The change is narrowly scoped and preserves newer recovery choices without changing scheduling defaults.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Walkthrough

The usage-limit continuation path now rejects a delegated child’s resume when its parent is settled and disarms the matching recovery. Tests cover stale continuation commands, and the thread sidebar guidance describes the cancellation rule.

Changes: Delegated usage-limit continuation

Layer / File(s) Summary
Parent settlement check and recovery handling
apps/server/src/orchestration-v2/Orchestrator.ts, apps/server/src/orchestration-v2/runtimeLayer.test.ts, docs/user/thread-sidebar.md
The orchestrator checks whether the child’s parent is settled before accepting an automatic continuation. It disarms matching recovery when the parent is settled. Tests verify that a stale command does not resume after the parent is unsettled or recovery is rearmed. The sidebar guidance states that settling a thread cancels a delegated task’s pending continuation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: High

Possibly related issues

  • pingdotgg/t3code#27944 — Reports delegated children resuming after their parent is settled. This change addresses the dispatch path, but does not change the sweep-level behavior described in the issue.

Possibly related PRs

  • pingdotgg/t3code#29838 — Changes usage-limit continuation attribution and delegated follow-up tracking in the same continuation path.
  • pingdotgg/t3code#28360 — Disarms usage-limit recovery when a thread settles.
  • pingdotgg/t3code#27951 — Prevents new recovery arming for a child of a settled parent; this change handles a recovery armed before the parent settled.

Suggested labels: bugfix

Suggested reviewers: t3dotgg, maria-rcks, fixfon

Merge Risk: Moderate · up to 0b048

A delegated task can resume after its parent was settled and then reopened, despite the intended cancellation. Retire the armed recovery on settlement before merging.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title names the main change: settling a parent cancels its delegated child's already-armed usage-limit auto-resume. It matches the code change in Orchestrator.dispatchMessage, the regression test,…
Linked Issues check Passed The linked issue #30144 has one active direct target and one coding requirement. The requirement is that settling a parent cancels, or skips at fire time, an already-armed usage-limit resume of its de…
Out of Scope Changes check Passed The reviewed changes touch three files: apps/server/src/orchestration-v2/Orchestrator.ts (settled-parent guard and recovery retirement), apps/server/src/orchestration-v2/runtimeLayer.test.ts (regressi…
Description check Passed The description includes all required sections. It clearly explains the problem, the implementation, scope and approval, focused verification results, limitations, and tool usage.
Full details: Title check

Explanation

The title names the main change: settling a parent cancels its delegated child's already-armed usage-limit auto-resume. It matches the code change in Orchestrator.dispatchMessage, the regression test, and the docs update. It uses the conventional commit format and avoids vague wording.

Full details: Linked Issues check

Explanation

The linked issue #30144 has one active direct target and one coding requirement. The requirement is that settling a parent cancels, or skips at fire time, an already-armed usage-limit resume of its delegated child, so the child does not run and the parent does not reopen. The reviewed changes implement this in apps/server/src/orchestration-v2/Orchestrator.ts. The dispatch guard reads the direct parent only for subagents and refuses the continuation when the parent is settled. A refused command that matches the armed recovery (run and request id) is retired with autoResume: false and a fresh request id. A stale command takes the no-op path and does not overwrite a newer re-arm. runtimeLayer.test.ts (retires a usage-limited subagent resume once its parent settles) covers the child run count, the parent state, reopen behavior, and stale replay. The incremental diff from the previous reviewed head to head for Orchestrator.ts returned no text, so the guard is unchanged since the prior PASS. The whole-PR diff could not be read because changed blobs are missing from the checkout, and a full file read of Orchestrator.ts was truncated before the guard, so the guard was not re-read directly at head. The prior conclusion and the current PR description are the evidence for the guard. The incremental changes to runtimeLayer.test.ts and docs/user/thread-sidebar.md were not read. The #27951 linked-environment lineage is excluded, as the issue text and PR description state.

Full details: Out of Scope Changes check

Explanation

The reviewed changes touch three files: apps/server/src/orchestration-v2/Orchestrator.ts (settled-parent guard and recovery retirement), apps/server/src/orchestration-v2/runtimeLayer.test.ts (regression and stale-command test for the same path), and docs/user/thread-sidebar.md (documents that settling a thread cancels pending continuations of tasks it delegated). Each change relates to #30144. The test and documentation support the guard. No unrelated implementation change was identified. The same limits as the linked-issue assessment apply: the whole-PR diff was unavailable, the Orchestrator.ts guard was not re-read at head, and the incremental test and documentation diffs were not read.

Finishing Touches
Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.
Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 1effe8b and 0b048fa.

Files selected for processing: 1
  • docs/user/thread-sidebar.md
Files skipped from review as they are similar to previous changes: 1
  • docs/user/thread-sidebar.md

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

@coderabbitai coderabbitai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Fix comments: Fix CodeRabbit comments on this PR

… settled at the reset

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@macroscopeapp
macroscopeapp Bot dismissed their stale review November 3, 2026 13:31

Dismissing prior approval to re-evaluate 0b048fa

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 5.0 KiB 5.0 KiB +16 B (+0.3%) 6.8 KiB ✅
Codex Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Codex Live turn WebSocket wire 1.2 KiB 1.2 KiB +16 B (+1.3%) 2.0 KiB ✅
Codex Live turn WebSocket decoded 21.5 KiB 21.6 KiB +41 B (+0.2%) 24.4 KiB ✅
Codex Live turn messages 1 2 +1 (+100.0%) 8 ✅
Claude Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Claude Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Claude Live turn WebSocket wire 1.2 KiB 1.2 KiB 0 B (0.0%) 2.0 KiB ✅
Claude Live turn WebSocket decoded 21.9 KiB 21.9 KiB 0 B (0.0%) 24.4 KiB ✅
Claude Live turn messages 2 2 0 (0.0%) 8 ✅

Baseline: cefb544 · PR result: 0b048fa · 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: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

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

@juliusmarminge juliusmarminge added bugfix Fix PR opened by an issue lead; enters the PR loop pr:gate-failed A land gate failed labels Nov 3, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Fix PR opened by an issue lead; enters the PR loop pr:gate-failed A land gate failed size:M 30-99 changed lines (additions + deletions). 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.

[Bug]: An armed usage-limit resume still runs a delegated child after its parent is settled, and reopens the parent

1 participant