Skip to content

release(stable): v4.260520.1 — agents survive VM reboot - #2461

Merged
namastex888 merged 13 commits into
mainfrom
dev
May 20, 2026
Merged

namastex888 merged 13 commits into
mainfrom
dev

Conversation

@namastex888

@namastex888 namastex888 commented May 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Promote dev → main for stable release v4.260520.1.

Headline fix: agents now survive Genie supervisor restarts (and by extension VM reboots).

Commits landing on main

  • 71b7d4bc — fix(scheduler): agents survive VM reboot — bridge JSONL session fallback + TMUX-presence shim
  • 12bd5f45 — fix(scheduler): route JSONL fallback through shouldResume chokepoint (CI fix; preserves invariant 3)
  • bc4053f9 — chore(version): bump to 4.260520.1 [auto-version]

What changed (one paragraph)

src/lib/scheduler-daemon.ts — runAgentRecoveryPass and defaultResumeAgent. The recovery pass previously filtered out every worker whose executors.claude_session_id was null, even though genie status resolves the session via a broader path (DB happy path → JSONL on-disk fallback via shouldResume). And defaultResumeAgent shelled genie agent resume without TMUX in env, so team-less dir: agents (the majority after a reboot) erroredout with "no team context" and the bare catch {} swallowed it. Both fixed; session UUID lookup now routes through the shouldResume chokepoint per invariant 3.

Reality check (validated on a reference host, pre-merge)

  • Before: every pm2 restart Genie → recovery_completed: resumed_agents: 0 (across 33 registered agents, 132 teams, 2729 sessions).
  • After: recovery_completed: resumed_agents: 17, failed_agents: 0 + 26 live tmux panes (was 2).
  • The remaining 12 no_session_id agents are genuine "JSONL not findable" cases handled by the runbook in UPGRADING-pgserve-v3.md.

CI / quality

  • All dev-channel CI green on commit 4054d11c.
  • Invariant 3 (state-machine.invariants.test.ts) passes.
  • Dev-channel release v4.260520.1 auto-tagged after merge, build + sign + publish in progress at PR-open time.

Test plan

  • Dev release v4.260520.1 publishes with signed tarballs (Release workflow #26144922962 in progress)
  • Dogfood install.sh from dev channel on a real host — confirm genie --version reports v4.260520.1 and a pm2 restart Genie auto-resumes registered agents
  • Spot-check recovery_completed event count after restart (genie events list --since 2m | grep recovery_completed) shows non-zero resumed_agents
  • Merge this PR → stable release v4.260520.1 publishes to @latest channel
  • Re-test from install.sh on stable channel to confirm public users get the same fix

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores

    • Bumped package and plugin versions to 4.260520.2.
    • Updated internal metrics/agent state and logs to reflect the latest run.
  • Bug Fixes

    • Improved agent recovery so sessions resume more reliably after reboots.
    • Better surface resume failures when GENIE_DEBUG_RESUME=1 to aid debugging.
    • Enhanced dead-pane handling and resume decisioning to reduce false negatives.

Review Change Stack

claude and others added 4 commits May 20, 2026 02:49
…ack + TMUX-presence shim

The oldest genie problem: after a VM reboot (or pm2 restart of Genie),
no agents auto-resumed. Reference instance: 33 agents, 20 classified
`eager_invoke` by the boot pass, `recovery_completed: resumed_agents: 0`
on every restart. The recovery infrastructure existed (`recoverOnStartup`
→ `runBootPass` → `runAgentRecoveryPass` → `attemptAgentResume` →
`defaultResumeAgent`), but two coupled defects silently killed it:

1. **runAgentRecoveryPass over-filtered.** The filter at line 1126 was
   `state != 'suspended/done' && currentSessionId`. But `currentSessionId`
   comes from the `executors.claude_session_id` join, which is null for
   most agents on a fresh boot — yet `genie status`'s classifier
   (`shouldResume`) resolves the session via a BROADER `getResumeSessionId`
   path: DB happy path → JSONL on-disk fallback (`~/.claude/projects/...`).
   So bootPass eager-invoked 20 agents whose JSONLs sat right there on
   disk, but the recovery pass filtered them out before the dead-pane
   check could see them. Fix: drop the `currentSessionId` filter clause,
   resolve inside the loop via `getResumeSessionId` for dead-pane
   survivors only (cheap), and enrich `worker.currentSessionId` in-place
   so attemptAgentResume can issue `claude --resume <uuid>`. SDK / non-tmux
   agents naturally fall out at attemptAgentResume's `no_session_id`
   early-exit when neither source yields a UUID.

2. **defaultResumeAgent shelled without TMUX.** `genie agent resume <id>`
   errors with `Cannot resume agent "<id>": no team context (template,
   agent record, env, or session). Pass --team or set GENIE_TEAM, or run
   inside a registered tmux session.` for `dir:`-prefixed agents whose
   `team` is NULL (29/33 on the reference instance). The scheduler is a
   pm2 child (never inside tmux) and shelled the CLI bare → exit 1 →
   execSync threw → bare `catch {}` swallowed everything → silent
   `agent_resume_failed`. The CLI's check is just env-presence (value
   isn't validated). Fix: pass a synthetic `TMUX` sentinel when the
   parent process has none, and surface previously-invisible CLI errors
   behind `GENIE_DEBUG_RESUME=1` instead of dropping them on the floor.

Validated on the reference instance (33 agents, post-fix Genie restart):
  recovery_completed → **resumed_agents: 17, failed_agents: 0** (was 0/0)
  26 live tmux panes on the canonical /tmp/tmux-1000/genie socket (was 2)
  20 agents in `spawning`/`idle` state in DB
  `genie ls` shows real registered agents with running panes
biome + check:fast green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI failure on PR #2460 caught by state-machine.invariants.test.ts
(invariant 3): only src/lib/should-resume.ts and the definition site
may import getResumeSessionId() — every consumer must read the
ShouldResumeResult instead. My fix imported the raw helper directly
in scheduler-daemon.ts, breaking the chokepoint contract.

Switch the dynamic import to `shouldResume` and read `decision.sessionId`.
Same semantics for the enrichment path (DB happy path → JSONL fallback),
respects the architectural contract, and incidentally gives the recovery
pass access to `decision.resume` if a future change wants to short-circuit
auto_resume_disabled / assignment_closed cases here too.

Validated:
- bun test src/__tests__/state-machine.invariants.test.ts → 1 pass / 0 fail
- bun run typecheck → clean
- biome check src/lib/scheduler-daemon.ts → clean

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
fix(scheduler): agents survive VM reboot — bridge JSONL session fallback + TMUX-presence shim
@coderabbitai

coderabbitai Bot commented May 20, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4abe0a9f-a045-452a-ad33-dcbbd418237c

📥 Commits

Reviewing files that changed from the base of the PR and between 3bd87d7 and 802ce49.

⛔ Files ignored due to path filters (5)
  • .genie/assets/commits-30d.svg is excluded by !**/*.svg
  • .genie/assets/loc-30d.svg is excluded by !**/*.svg
  • .genie/assets/releases-30d.svg is excluded by !**/*.svg
  • README.md is excluded by !*.md
  • VELOCITY.md is excluded by !*.md
📒 Files selected for processing (8)
  • .claude-plugin/marketplace.json
  • .genie/agents/metrics-updater/daily-stats.jsonl
  • .genie/agents/metrics-updater/runs.jsonl
  • .genie/agents/metrics-updater/state.json
  • package.json
  • plugins/genie/.claude-plugin/plugin.json
  • plugins/genie/package.json
  • src/lib/scheduler-daemon.ts

📝 Walkthrough

Walkthrough

Bump manifests to 4.260520.2, append metrics-updater run and daily stat entries, and modify scheduler daemon resume/recovery to inject a synthetic TMUX, optionally surface resume CLI stderr, relax the resumable pre-filter, and consult shouldResume per worker before handling dead panes.

Changes

Release 4.260520.2

Layer / File(s) Summary
Version metadata synchronization
.claude-plugin/marketplace.json, package.json, plugins/genie/.claude-plugin/plugin.json, plugins/genie/package.json
Version fields bumped from 4.260519.3 to 4.260520.2 across root and plugin manifests.
Metrics agent records and state
.genie/agents/metrics-updater/daily-stats.jsonl, .genie/agents/metrics-updater/runs.jsonl, .genie/agents/metrics-updater/state.json
Appended a new daily stats entry (2026-05-20), added a run log for the metrics-updater execution, and advanced persisted agent state (last_run, daily_stats_count, duration_ms).
Scheduler resume & recovery logic
src/lib/scheduler-daemon.ts
defaultResumeAgent injects a synthetic TMUX env for scheduler-invoked genie agent resume and will write CLI stderr when GENIE_DEBUG_RESUME=1. runAgentRecoveryPass loosens the resumable pre-filter (excludes terminal/archived states and validates paneId shape) and resolves shouldResume once per pass; each worker consults shouldResume(worker.id), may skip in boot mode when decision.resume is false (except unknown_agent), and will populate currentSessionId from decision.sessionId only if missing before calling handleDeadPane.

Sequence Diagram(s)

sequenceDiagram
  participant SchedulerDaemon
  participant ShouldResume
  participant GenieCLI
  participant HandleDeadPane
  SchedulerDaemon->>ShouldResume: shouldResume(worker.id)
  ShouldResume-->>SchedulerDaemon: decision { resume, sessionId, reason }
  alt decision.resume == false (boot mode) and decision.reason != "unknown_agent"
    SchedulerDaemon->>SchedulerDaemon: skip worker processing
  else decision.resume == true
    alt decision.sessionId present && worker.currentSessionId missing
      SchedulerDaemon->>SchedulerDaemon: set worker.currentSessionId = decision.sessionId
    end
    SchedulerDaemon->>GenieCLI: spawn "genie agent resume" (env includes synthetic TMUX)
    GenieCLI-->>SchedulerDaemon: exit / error (emit to stderr if GENIE_DEBUG_RESUME=1)
    SchedulerDaemon->>HandleDeadPane: handleDeadPane(worker)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • automagik-dev/genie#666: Related changes to agent/session resumption behavior (resume/session naming differences).
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the main change: a version bump release with the headline fix that agents now survive VM reboots, which is the primary objective of this changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@package.json`:
- Line 3: The release version bump in the "version" field (currently
"4.260520.1") must be blocked until the pinned integrity hashes are updated;
locate any binarySha256 pins referenced in package.json or related install/asset
metadata (search for "binarySha256" or asset entries) and update those SHA256
values to match the actual artifact(s) used by CI, recomputing the correct
SHA256 from the released asset(s) and replacing the stale pin(s); only then
re-apply the version bump and re-run CI to confirm the SHA drift error is
resolved.

In `@src/lib/scheduler-daemon.ts`:
- Around line 1179-1181: The code currently swallows errors from shouldResume
(using .catch(() => null)) which makes transient resolver/DB failures look like
“no session” and causes attemptAgentResume to treat them as permanent no-session
state; change the call to shouldResume(worker.id) so errors are not converted to
null—either remove the .catch entirely or rethrow the caught error (so the
exception propagates) so that calling logic treats resolver failures as worker
failures rather than as missing session IDs; touch the block around
shouldResume, decision, enriched and ensure attemptAgentResume only runs when
decision is explicitly present (decision?.sessionId) and not when shouldResume
errored.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 19dab3ef-6ee9-4592-aa90-b89743f6e661

📥 Commits

Reviewing files that changed from the base of the PR and between 9521906 and bc4053f.

📒 Files selected for processing (5)
  • .claude-plugin/marketplace.json
  • package.json
  • plugins/genie/.claude-plugin/plugin.json
  • plugins/genie/package.json
  • src/lib/scheduler-daemon.ts

Comment thread package.json Outdated
{
"name": "@automagik/genie",
"version": "4.260519.3",
"version": "4.260520.1",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Block release bump until SHA drift is resolved.

Line 3 promotes a stable release, but CI reports SHA drift for $asset; that means pinned binarySha256 integrity data is currently stale and release verification is not trustworthy. Update the affected SHA pin(s) before merging this version bump.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` at line 3, The release version bump in the "version" field
(currently "4.260520.1") must be blocked until the pinned integrity hashes are
updated; locate any binarySha256 pins referenced in package.json or related
install/asset metadata (search for "binarySha256" or asset entries) and update
those SHA256 values to match the actual artifact(s) used by CI, recomputing the
correct SHA256 from the released asset(s) and replacing the stale pin(s); only
then re-apply the version bump and re-run CI to confirm the SHA drift error is
resolved.

Comment thread src/lib/scheduler-daemon.ts Resolved

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bc4053f99c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/scheduler-daemon.ts Outdated
Comment on lines +1180 to +1181
const decision = await shouldResume(worker.id).catch(() => null);
if (decision?.sessionId) enriched = { ...worker, currentSessionId: decision.sessionId };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Respect shouldResume decision before injecting session IDs

runAgentRecoveryPass now calls shouldResume(worker.id) but only uses decision.sessionId, ignoring decision.resume. In should-resume.ts, sessionId can be present even when resume is explicitly disallowed (e.g., assignment_closed or auto_resume_disabled), so this code can re-enable resume attempts for agents that should not be resumed whenever currentSessionId is null but JSONL fallback finds a UUID. That can relaunch completed/closed task agents after reboot and duplicate side effects.

Useful? React with 👍 / 👎.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request increments the version to 4.260520.1 and enhances the agent recovery logic in scheduler-daemon.ts. Key improvements include adding a TMUX environment variable shim to allow agent resumption from the scheduler and updating the recovery pass to resolve session IDs for agents missing them after a reboot. A review comment suggests optimizing the recovery loop by moving a dynamic import outside the loop and ensuring that rehydration hints and the shouldResume chokepoint are correctly respected for all workers.

Comment on lines 1163 to +1183

for (const worker of resumable) {
try {
const alive = await deps.isPaneAlive(worker.paneId);
const hasTmuxPane = typeof worker.paneId === 'string' && /^%\d+$/.test(worker.paneId);
const alive = hasTmuxPane ? await deps.isPaneAlive(worker.paneId) : false;
if (alive) continue;
const outcome = await handleDeadPane(deps, resolvedConfig, daemonId, worker, turnAware, mode);

// Dead pane (or never had one — e.g. fresh-from-DB after a VM
// reboot). Bridge `currentSessionId` via the `shouldResume` chokepoint
// so attemptAgentResume can spawn `claude --resume <uuid>` against an
// on-disk JSONL the DB-join didn't surface. Going through the
// chokepoint (vs. calling `getResumeSessionId` directly) preserves
// invariant 3 in `state-machine.invariants.test.ts` — only
// `should-resume.ts` and the definition site may read the raw helper.
let enriched = worker;
if (!enriched.currentSessionId) {
const { shouldResume } = await import('./should-resume.js');
const decision = await shouldResume(worker.id).catch(() => null);
if (decision?.sessionId) enriched = { ...worker, currentSessionId: decision.sessionId };
}

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.

medium

The dynamic import of shouldResume is currently performed inside the worker loop. Since runAgentRecoveryPass can iterate over many workers, performing an await import() in each iteration adds unnecessary overhead, even if the module is cached by Node.js. It is more efficient to import the function once before the loop starts.

Furthermore, the current implementation only calls shouldResume for workers missing a session ID, and it ignores the resume flag and rehydrate hint. To properly enforce the "chokepoint" design and align with the PR's stated goal of eager vs. lazy rehydration:

  1. shouldResume should be called for all dead-pane survivors to ensure that assignment status and other resume preconditions are checked.
  2. The enriched worker should only carry a currentSessionId if decision.resume is true and, in boot mode, if the agent is marked for eager rehydration. This prevents auto-spawning task-bound agents that should only be surfaced in status.
  const { shouldResume } = await import('./should-resume.js');
  for (const worker of resumable) {
    try {
      const hasTmuxPane = typeof worker.paneId === 'string' && /^%\d+$/.test(worker.paneId);
      const alive = hasTmuxPane ? await deps.isPaneAlive(worker.paneId) : false;
      if (alive) continue;

      // Dead pane (or never had one — e.g. fresh-from-DB after a VM
      // reboot). Bridge currentSessionId via the shouldResume chokepoint
      // so attemptAgentResume can spawn claude --resume <uuid> against an
      // on-disk JSONL the DB-join didn't surface. Going through the
      // chokepoint (vs. calling getResumeSessionId directly) preserves
      // invariant 3 in state-machine.invariants.test.ts — only
      // should-resume.ts and the definition site may read the raw helper.
      const decision = await shouldResume(worker.id).catch(() => null);

      // Respect the chokepoint decision and rehydration hint. We only provide
      // a sessionId if resume is permitted and, in boot mode, if the agent is
      // marked for eager rehydration. Task-bound agents (lazy) are surfaced
      // in status but not auto-resumed here.
      const canResume = decision?.resume && (mode !== 'boot' || decision.rehydrate === 'eager');
      const enriched = { ...worker, currentSessionId: canResume ? decision.sessionId : null };
References
  1. Use dynamic imports (require() or import()) to break circular dependency cycles that would otherwise occur at module load time.

Three bot-reviewed correctness fixes on the v4.260520.1 recovery work.

HIGH-1: codex P1 + gemini M on PR #2461.
shouldResume returns sessionId for assignment_closed and
auto_resume_disabled rows (for genie status display), not only
for the resumable ok case. Previous code gated enrichment on
decision?.sessionId alone, so a task agent whose latest assignment
was done/failed would be re-invoked on every reboot — JSONL on disk,
sessionId resurfaced, currentSessionId enriched, attemptAgentResume
re-spawned. Fix: gate on decision.resume && decision.sessionId.

HIGH-2: codex P1 on PR #2460.
Dropping the currentSessionId clause widened the prefilter to
state-only, so SDK/inline workers (paneId = synthetic id like
'sdk', not %N) entered the loop. With hasTmuxPane=false, alive
was false and the worker fell into handleDeadPane. In sweep mode
with turn-aware enabled, an idle SDK worker hits
terminalizeCleanExitUnverified — a state-mutating write that
corrupts live executor state for healthy non-tmux workers.
Fix: prefilter on paneId shape (^%\d+$ only). Mirrors the
canonical transport gate in countActiveWorkers. Also adds
archived to excluded states (gemini M on #2460).

MEDIUM: CodeRabbit major on PR #2461.
.catch(() => null) on shouldResume converted transient resolver/DB
failures to decision=null, the worker fell through unenriched,
attemptAgentResume counted it as no_session_id and incremented the
retry budget. After 3 transient blips a healthy worker's auto_resume
flipped to false. Fix: let shouldResume throw — the outer try/catch
routes through handleRecoveryProbeError or increments failed.

LOW polish: hoisted shouldResume import outside loop (gemini M
on #2461). Added archived to excluded states.

Validated:
- invariant 3 in state-machine.invariants.test.ts: pass
- bun test --test-name-pattern recovery: 10 pass / 0 fail
- bun run typecheck: clean
- biome check src/lib/scheduler-daemon.ts: clean

Ignored CodeRabbit major "SHA drift on package.json" on #2461 —
false positive, Binary SHA Drift workflow is not running on this
PR. 17/17 CI green on #2461.

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

fix(scheduler): respect shouldResume.resume + gate recovery on transport

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/scheduler-daemon.ts`:
- Around line 1207-1211: The current logic only calls shouldResume(worker.id)
when enriched.currentSessionId is falsy, which allows dead panes that already
have a session id to skip the resume decision; change the flow so that
shouldResume(worker.id) is consulted for every dead pane (regardless of
enriched.currentSessionId) and short-circuit handling when decision.resume !==
true (i.e., only proceed to boot-mode recovery/handleDeadPane if decision.resume
is true and decision.sessionId is present), updating the use of
enriched/currentSessionId assignment accordingly to use decision.sessionId when
resuming; references: enriched.currentSessionId, shouldResume(worker.id),
decision.resume, decision.sessionId, handleDeadPane.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 441c4b57-fcaa-4e8f-8836-34197ad1b83c

📥 Commits

Reviewing files that changed from the base of the PR and between bc4053f and 6d10a7b.

📒 Files selected for processing (1)
  • src/lib/scheduler-daemon.ts

Comment thread src/lib/scheduler-daemon.ts Resolved
CodeRabbit follow-up on PR #2461 (line 1211, posted 07:03Z).

The v4.260520.2 fix only called shouldResume when currentSessionId was
missing. Task agents with a closed assignment (decision.resume=false,
reason=assignment_closed) usually KEEP their currentSessionId from the
executor join — so they bypassed the chokepoint entirely and fell into
handleDeadPane(mode='boot'). isLegitimatelyClosed there checks the
executor's outcome, not the assignment's outcome, and misses the case
when the agent finished its task but the executor didn't terminalize
cleanly. Result: a task agent the boot pass had classified as
lazy_surface gets re-invoked on reboot. Exact regression class the
chokepoint exists to prevent.

Fix:
- Always consult shouldResume for dead-pane survivors.
- Boot mode: short-circuit when decision.resume is false (matches the
  boot pass's lazy_surface/skip classification upstream).
- Sweep mode: unaffected. Idle dead-pane → terminalizeCleanExitUnverified
  is the correct cleanup for done agents; the existing handleDeadPane
  path keeps doing its thing.

Adds one shouldResume call per dead-pane survivor in the worst case
(was: only null-session ones), but the set is narrow — most workers
stay alive across a tick.

Validated:
- invariant 3 in state-machine.invariants.test.ts: pass
- bun test --test-name-pattern recovery: 10 pass / 0 fail
- bun run typecheck: clean
- biome check src/lib/scheduler-daemon.ts: clean

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
namastex888 and others added 2 commits May 20, 2026 04:17
…epoint-for-sessioned-panes

fix(scheduler): consult shouldResume for every dead pane in boot mode
CI red on PR #2461 caught by scheduler-daemon.test.ts and
auto-resume-zombie-cap.test.ts after PR #2463 landed.

Root cause: PR #2463 added a chokepoint call for every dead-pane
survivor and short-circuited boot mode when decision.resume is false.
Tests mock listWorkers but don't seed the agents table, so
readAgentResumeRow returns null and shouldResume returns
{ resume: false, reason: 'unknown_agent' } — the short-circuit then
drops fixtures the test expects to be resumed.

In production this is impossible: listWorkers and readAgentResumeRow
both read the agents table, so a row visible to the first read is
visible to the second. The unknown_agent branch only fires on test
fixtures that intentionally bypass the chokepoint's data source.

Fix: exclude decision.reason === 'unknown_agent' from the
short-circuit. Production semantics unchanged (case impossible);
legacy test fixtures restored. A real production race (row visible
to one read, invisible to the other) still falls through to
attemptAgentResume which has its own guards.

Validated:
- bun test scheduler-daemon.test.ts: 118 pass / 0 fail (was 2 fail)
- bun test auto-resume-zombie-cap.test.ts: pass (was undefined fail)
- bun test state-machine.invariants.test.ts: 1 pass / 0 fail
- bun run typecheck: clean
- biome check: clean

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@namastex888
namastex888 merged commit 813827f into main May 20, 2026
20 checks passed
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.

2 participants