Skip to content

Keep agents with live background work out of hibernation - #15278

Merged
teamleaderleo merged 6 commits into
manaflow-ai:mainfrom
teamleaderleo:hibernation/keep-background-work
Sep 30, 2026
Merged

teamleaderleo merged 6 commits into
manaflow-ai:mainfrom
teamleaderleo:hibernation/keep-background-work

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Agent Hibernation could kill work an agent was still waiting on. The only background-work signal was Claude's Stop payload (background_tasks running or session_crons). A pane that Stop reported idle, whose agent still had a run_in_background shell, a Monitor loop or an async subagent, passed every check. Teardown then sent SIGTERM to the agent's whole descendant scope, background work included.

Two guards now keep such panes awake.

  • Live background shells (process scope). A shell (zsh, bash, sh, fish, ...) under the agent that started more than 30 s after the agent marks the scope unsafe to terminate. Children started at launch, such as MCP servers and launch hooks, are the baseline and don't count. This runs inside agentHibernationProcessScope, so it reaches three checks: the planner, the confirmation re-check, and the fresh-index validation just before any signal. It applies to scheduled and memory-pressure reclaim alike.
  • Unfinished background launches (transcript). Before the teardown snapshot, the tail of the Claude transcript (32 MB) is scanned. The scan looks for tool results that started background work (toolUseResult.backgroundTaskId for Bash, taskId for Monitor, isAsync + async_launched for Agent) with no terminal <task-notification> naming the same tool-use id or task id.
    • Monitor event notifications carry no <status>, so they don't count as completion.
    • Notification text quoted inside a tool result doesn't count either.
    • Launches from before the current agent process started are ignored, because that work died with the old process.
    • A pane with pending work gets the existing 120 s retry marker instead of being torn down.

The transcript scan covers async subagents, which run inside the Claude process and have no process of their own. The process check covers older Claude builds and shells the transcript doesn't describe.

I checked the transcript format against local transcripts from Claude Code 2.1.283 (about 3k notifications). A batched "4 background agents stopped" notification lists only <task-id>s, which is why completion also matches by task id. On live sessions the scanner reported exactly the subagents and shells that were still writing output.

Part of the hibernation safety work tracked in manaflow-ai/cmuxterm-hq#880.

Testing

  • 123ca0ff adds AgentHibernationBackgroundWorkTests, written against main's API. Expected on main: lateShellUnderTheAgentMakesTheScopeUnsafe, unfinishedBackgroundBashBlocksTheTeardownSnapshot and unfinishedAsyncAgentBlocksTheTeardownSnapshot fail.
  • 9ac1df0a adds the fix. It tightens those tests to expect .backgroundWorkPending and adds pure scanner cases: batched task-id notifications, the pre-start cutoff, Monitor events and quoted notifications.
  • Locally (Linux, no app build): I compiled the transcript scanner standalone with Swift 6.1 and ran the three pure scanner cases (all pass). python3 scripts/verify-local.py passes: syntax, project, wiring, test wiring, feature flags.
  • App compilation and the cmuxTests run happen in CI; I haven't run them locally.

Changelog

Fixed: Agent Hibernation no longer terminates an agent that still has a background command, Monitor or subagent running

Checklist

  • Behavior changes have added or updated tests, or Testing says why not
  • User-facing docs updated if needed (docs/agent-hooks.md eligibility list)
  • Reviewed with a subagent before merge

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Stops hibernation from tearing down agents that still have background work running. Previously a pane was only kept awake if Claude's Stop payload reported running background tasks, so agents with live background shells, Monitor loops, or async subagents passed every check and teardown terminated that work.

Adds two guards that keep such panes out of hibernation:

  • Process check. A shell started by the agent more than 30 s after the agent itself marks the scope unsafe to terminate. Launch-time children like MCP servers are the baseline and don't count, and an MCP server's own sh -c under node isn't agent work. Applies to the planner, confirmation re-check, and final pre-signal validation for scheduled and memory-pressure reclaim.
  • Transcript check. Before the teardown snapshot, the transcript tail (32 MB) is scanned for background launches — run_in_background Bash, Monitor taskId with Monitor-only keys, async Agent launches — with no terminal <task-notification> for the same tool-use or task id. Todo tool results that also carry a taskId don't count as launches, a TaskStop result finishes the stopped task without a notification, Monitor events and quoted notifications don't count as completion, and launches predating the current agent process are ignored. With no live agent process there is nothing to kill, so recorded launches don't keep an exited pane awake. A pane with pending work gets the existing retry marker instead of being torn down.

Written for commit 7046d32. Summary will update on new commits.

Review in cubic

teamleaderleo and others added 2 commits September 28, 2026 02:18
A pane whose agent still has a background shell, Monitor or async subagent
passes every hibernation check today, so teardown terminates that work with
the agent.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Hibernation treated a pane as done once the Stop hook said idle, then
terminated the agent's whole descendant scope. Background shells, Monitor
loops and async subagents that the Stop payload did not report were killed.

Two guards now keep such panes awake:
- Process scope: a shell under the agent that started more than 30 s after
  the agent (launch-time children such as MCP servers are the baseline) marks
  the scope unsafe to terminate. This covers the planner, the confirmation
  re-check and the final pre-signal validation, for scheduled and pressure
  reclaim alike.
- Transcript: before the teardown snapshot, the Claude transcript tail is
  scanned for background launches (toolUseResult backgroundTaskId, Monitor
  taskId, async Agent launches) without a terminal <task-notification>.
  Launches from before the current agent process started are ignored. The
  pane gets the existing retry marker instead of being torn down.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 10 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: ccbaca74-5cc3-4756-ac22-038e349c0853

📥 Commits

Reviewing files that changed from the base of the PR and between 478e323 and 7046d32.

📒 Files selected for processing (11)
  • Sources/App/AgentHibernationController+ProcessTermination.swift
  • Sources/App/AgentHibernationController+Records.swift
  • Sources/App/AgentHibernationController+Teardown.swift
  • Sources/App/AgentHibernationTranscriptGuard+BackgroundWork.swift
  • Sources/App/AgentHibernationTranscriptGuard+TeardownSnapshotOutcome.swift
  • Sources/App/AgentHibernationTranscriptGuard.swift
  • Sources/CmuxTopSnapshot+AgentBackgroundWork.swift
  • Sources/CmuxTopSnapshot.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/AgentHibernationBackgroundWorkTests.swift
  • docs/agent-hooks.md

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

- Only a Monitor result (taskId with timeoutMs/persistent) counts as a
  Monitor launch; todo tools also return a taskId.
- A TaskStop result finishes the stopped task even without a notification.
- With no live agent process there is nothing to kill, so recorded launches
  no longer keep an exited pane awake.
- Only shells whose parent is the agent count as work; an MCP server's
  sh -c under node does not.
- Prefilter lines on launch-specific keys and build date formatters once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (subagent, correctness first):

  • No compile or concurrency blockers. The record property is MainActor-isolated and only read inside the MainActor snapshotOutcomes. The task-group captures are Sendable. The only exhaustive switch over TeardownSnapshotOutcome is updated. The test initializers match.
  • Red/green: at 123ca0f, lateShellUnderTheAgentMakesTheScopeUnsafe, unfinishedBackgroundBashBlocksTheTeardownSnapshot and unfinishedAsyncAgentBlocksTheTeardownSnapshot fail on main and pass with the fix.

Fixed in 45a8ecd (Tighten background-work detection after review):

  1. Any toolUseResult.taskId counted as a Monitor launch, and todo tools return a taskId too, so those panes would never hibernate. Now a Monitor launch needs timeoutMs or persistent next to the taskId. Added todoTaskIDsAreNotBackgroundLaunches.
  2. An exited agent pane was blocked by stale launches, because its empty process scope meant there was no start-time cutoff. With no live agent process, the scan now ignores every recorded launch, since there is nothing left to kill.
  3. A TaskStop could close a task without a terminal notification. TaskStop results (task_id + task_type) now finish the task. Added taskStopFinishesTheStoppedTask.
  4. The shell guard had false positives: an MCP server's sh -c under node that restarts late kept the pane awake forever. Now only shells whose parent is the agent count. Added lateShellUnderAnMCPServerDoesNotCountAsBackgroundWork.
  5. Scan cost: lines are now prefiltered on launch-specific keys instead of any toolUseResult, and the date formatters are built once per scan.

Left:

  • The cutoff uses the oldest process in the termination scope, not the agent root, so an older process-group leader can pull in a previous process's launches. This errs toward keeping a pane awake. The record doesn't carry agent-root identities today, and threading them through isn't worth it for this.
  • A TaskOutput read that suppresses the completion notification: there are only 2 TaskOutput calls across local transcripts, and I couldn't confirm the shape, so it isn't handled. The process guard still sees any live shell.
  • The shell guard is Claude-specific in practice. Codex's bash -lc gets exec-optimized, so no shell survives for it to see.

I re-ran the updated scanner on 72 local transcripts. The pending sets match work that is still running (subagent transcripts written minutes earlier, or live background shells).

@cursor

cursor Bot commented Sep 28, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

Catch-up merge by scripts/ci/catch_up_pr.py (RFC manaflow-ai#14631).
Merged by scripts/merge-main.sh: origin/main at 0c753fe.

Resolved conflicts:
- cmux.xcodeproj/project.pbxproj: union of added entries, then normalize-pbxproj.py

Catch-up-previous-head: 45a8ecd
Catch-up-base: 0c753fe
Catch-up merge by scripts/ci/catch_up_pr.py (RFC manaflow-ai#14631).
Merged by scripts/merge-main.sh: origin/main at 94a6387.

Resolved conflicts:
- cmux.xcodeproj/project.pbxproj: union of added entries, then normalize-pbxproj.py

Catch-up-previous-head: 32d6b13
Catch-up-base: 94a6387
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

CI failure attribution

CI passes on 7046d32658 (run 36686097390 attempt 1).

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's.

Catch-up merge by scripts/ci/catch_up_pr.py (RFC manaflow-ai#14631).
Merged by scripts/merge-main.sh: origin/main at 478e323.

Resolved conflicts:
- cmux.xcodeproj/project.pbxproj: union of added entries, then normalize-pbxproj.py

Catch-up-previous-head: 0b83c89
Catch-up-base: 478e323

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@teamleaderleo
teamleaderleo merged commit 2761cc9 into manaflow-ai:main Sep 30, 2026
65 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

Merge receipt for 7046d32658: every check was green at merge (17 verified; 20 skipped by policy). Full suite runs on main after merge.

rustybret pushed a commit to rustybret/bmux that referenced this pull request Sep 30, 2026
0e44675 test: bound remote bootstrap subprocess waits (manaflow-ai#15608)
a192a14 fix(agent-chat): show ACP plans as structured step lists (manaflow-ai#15889)
d7f59a3 ci: place attempt 2 like attempt 1, owned minis first (manaflow-ai#15406)
d87c3be feat(agent-chat): register Cursor Agent as an ACP provider (manaflow-ai#15877)
1bd5083 fix: preserve Codex provider for workspace auto-naming (manaflow-ai#15635)
03e1245 fix(worktree-seed): budget each pattern and refuse dangling escapes (manaflow-ai#15860)
5c28fcb fix(agent-chat): stop a disposed ACP session from resurrecting its agent (manaflow-ai#15872)
11216d2 Fix Codex Agent Chat Stop interrupt request (manaflow-ai#15837)
d6b8c15 ci: watch Unix cmux-tui installer changes (manaflow-ai#15874)
0fc35d6 feat(agent-chat): register goose as an ACP provider (manaflow-ai#15871)
7f27bfc cmux ssh: security hardening from the ssh audit (manaflow-ai#15768)
8599250 fix(agent-chat): launch gemini with --experimental-acp (manaflow-ai#15868)
849376a docs: classify contributor issue difficulty (manaflow-ai#15627)
2761cc9 Keep agents with live background work out of hibernation (manaflow-ai#15278)
eae02a6 Cloud: rebake the devbox ladder with cmux-tui 02dac3c (manaflow-ai#15866)
7ed2f6b ci: bound open pull-request media revisions (manaflow-ai#15861)
13c417c Notify on SubagentStop in the notifications hook docs (manaflow-ai#15854)
5cfc6a6 fix: make cmux-tui installs immutable across release uploads (manaflow-ai#15859)
4eee1b1 fix: preserve longest Claude upstream cooldown (manaflow-ai#15856)
204b936 Pin Cloud panes to the daemon's terminal grid (manaflow-ai#15792)
fc13b7c cmux-tui: fix the replay row scroll and stale hook fence tests breaking the full gate (manaflow-ai#15240)
87d66af Add Cloud to the menu bar extra and a main-menu Cloud menu (manaflow-ai#15822)

# Conflicts:
#	.github/workflows/ci-failure-attribution.yml
#	.github/workflows/ci-macos.yml
#	.github/workflows/ci-owned-pool-rescue.yml
#	.github/workflows/ci-queue-janitor.yml
#	.github/workflows/ci.yml
#	.github/workflows/cmux-tui-artifacts.yml
#	.github/workflows/cmux-tui-build-package.yml
#	.github/workflows/cmux-tui-sdks.yml
#	.github/workflows/pr-media-prune.yml
#	.github/workflows/remote-daemon.yml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant