fix(coding-agent): harden daemon startup and recovery ownership - #1929
Merged
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 59e09c1. Configure here.
…icts Adoption and recovery classify liveness through processIdentity() instead of re-pairing isProcessAlive+getProcessStartId at each site; raw start-id reads remain only where the value itself is persisted. The sole surviving kill of a live worker is the identity-verified pre-roster replacement, now pinned: adoption of a live pre-roster worker still kills-and-relaunches, while an unverifiable identity still parks failed with no replacement.
A hung-but-alive worker (or a live pid with no verifiable start id) probed forever: ~11s probe pass, 5s defer, repeat. After MAX_DEFERRED_RECOVERY_ROUNDS (10 rounds, ~2.5 minutes) the worker parks failed — user-visible through the existing roster failed status — with its process left alive for a manual retry_worker, which resets the round count like any successful recovery.
isDaemonWorkerProbeTimeout classified by message prefixes defined in two other files. The timeout sites in daemon-worker-client.ts (hello, response) and the supervisor's connect deadline now throw DaemonWorkerProbeTimeoutError, and the classifier is a plain instanceof — one truth, same shape as DaemonWorkerAuthenticationError.
The recovery SIGKILL removal applies to every failure class, not just probe timeouts. Verified: a live verified-identity failed worker IS reclaimed by the next fresh create (graceful stop), so the true leak is only the unverifiable survivor — the deliberate fail-safe. One comment at the decision point and a changelog line make the tradeoff explicit.
snimu
force-pushed
the
fix/daemon-recovery-hardening
branch
from
September 1, 2026 11:13
fe1062a to
c86123d
Compare
The carve ran recoverUncertainWorkerOperations (interruption marking, orphan reaping) before the identity-gated SIGKILL, so destructive cleanup hit a still-active worker; pre-rebase the kill preceded cleanup inside the old helper. Reordered: recheck + SIGKILL + bounded teardown wait first, cleanup only after the predecessor is confirmed gone/replaced, and an unverifiable survivor parks failed with NO destructive cleanup at all (matching recovery's rule that a possibly-live worker is never cleaned destructively). The carve pin asserts the order and the no-cleanup branch.
proper-lockfile verified: compromise detection is timer-driven (mtime updates), and release() removes the lockfile unconditionally when the steal has not been detected yet — so a caller resuming from a stall past the stale threshold could delete the successor'"'"'s lock. The guard directory'"'"'s inode is the ownership identity (a steal is rmdir+mkdir): it is captured at acquisition and checked synchronously where the timer cannot run — before returning an action'"'"'s result and before release. A stolen-but-undetected guard is left to its own updater, which notices the foreign mtime and cleans itself. A truly synchronous stall still cannot be preempted mid-action; that limitation is stated at the guard.
snimu
approved these changes
Sep 1, 2026
olety
added a commit
to oneiron-dev/prime-agent
that referenced
this pull request
Sep 1, 2026
Takes upstream's event-driven supervisor roster ledger + push (PrimeIntellect-ai#1897, PrimeIntellect-ai#1900), direct TUI<->worker transport (ENG-5817), daemon startup/recovery hardening (PrimeIntellect-ai#1929, PrimeIntellect-ai#1909), single-dump kernel snapshots (PrimeIntellect-ai#1945), empty-draft eviction (PrimeIntellect-ai#1946), rlm_child_update suppression (PrimeIntellect-ai#1944), bash-skill preview (PrimeIntellect-ai#1911). Fork laws re-expressed on the roster architecture: - stable-target follow-up honesty kept (capability proof via worker hello, target_unavailable never not_found when ownership unproven) - schema revision 26 (union of fork rev-24 stable-target + upstream rev-24/25 roster+transport); digest minted by the repo's own algorithm - summary freshness reuse + single-flight + staleness + root-omission rejection restored on upstream's refresh pull - adoption/recovery never fails a live worker on a slow or root-omitting catalog: get_state root seed + stale mark + bounded background rehydration - repl.py keeps fork prune-on-aggregate-overflow - delete handlers keep fork persistence reporting; eviction fence test keeps the stronger two-worker contention variant Known test debt (deferred to post-Wave cleanup per owner): roster-era fixture migrations in daemon-supervisor-monitor (2), plus un-triaged failures in package-command-paths, agent-session-recursion, daemon-runtime-stress, 4600-supervisor-singleton, 4603-worker-recovery, 4606-update-restart- coordinator, agents-view-roster. Production laws preserved; failures are fixture-era artifacts or mechanism assertions to rewrite.
ketema
added a commit
to ketema/prime-agent
that referenced
this pull request
Sep 1, 2026
- Direct session transport between TUI and worker (ENG-5817, PrimeIntellect-ai#1926) - Event-driven supervisor agent roster with push subscriptions (PrimeIntellect-ai#1897, PrimeIntellect-ai#1900, PrimeIntellect-ai#1895) - Hardened daemon startup, recovery ownership, and worker launch diagnostics (PrimeIntellect-ai#1929, PrimeIntellect-ai#1918) - Python REPL runtime single-dump snapshots and bash preview tool (PrimeIntellect-ai#1945, PrimeIntellect-ai#1911) - Non-blocking RLM subagent deletion and snapshot update suppression (PrimeIntellect-ai#1954, PrimeIntellect-ai#1944) - Saved catalog loading on Agents View open (PrimeIntellect-ai#1960) - Advanced Anthropic prompt caching marker across tool results (PrimeIntellect-ai#1927) - TUI process replacement on update and empty draft eviction (PrimeIntellect-ai#1631, PrimeIntellect-ai#1946, PrimeIntellect-ai#1920)
paralin
pushed a commit
to paralin/prime-agent
that referenced
this pull request
Sep 2, 2026
…eIntellect-ai#1929) * fix(coding-agent): harden daemon recovery and socket ownership * fix(coding-agent): address daemon recovery review findings * fix(coding-agent): preserve prompt admission scheduling * test(coding-agent): consolidate daemon recovery coverage * fix(coding-agent): preserve safe shutdown outcomes * fix(coding-agent): close recovery admission gaps * refactor(coding-agent): one process-identity oracle for recovery verdicts Adoption and recovery classify liveness through processIdentity() instead of re-pairing isProcessAlive+getProcessStartId at each site; raw start-id reads remain only where the value itself is persisted. The sole surviving kill of a live worker is the identity-verified pre-roster replacement, now pinned: adoption of a live pre-roster worker still kills-and-relaunches, while an unverifiable identity still parks failed with no replacement. * fix(coding-agent): bound live-worker probing at ten defer rounds A hung-but-alive worker (or a live pid with no verifiable start id) probed forever: ~11s probe pass, 5s defer, repeat. After MAX_DEFERRED_RECOVERY_ROUNDS (10 rounds, ~2.5 minutes) the worker parks failed — user-visible through the existing roster failed status — with its process left alive for a manual retry_worker, which resets the round count like any successful recovery. * refactor(coding-agent): type the worker probe timeout isDaemonWorkerProbeTimeout classified by message prefixes defined in two other files. The timeout sites in daemon-worker-client.ts (hello, response) and the supervisor's connect deadline now throw DaemonWorkerProbeTimeoutError, and the classifier is a plain instanceof — one truth, same shape as DaemonWorkerAuthenticationError. * docs(coding-agent): state the leak-over-kill recovery tradeoff The recovery SIGKILL removal applies to every failure class, not just probe timeouts. Verified: a live verified-identity failed worker IS reclaimed by the next fresh create (graceful stop), so the true leak is only the unverifiable survivor — the deliberate fail-safe. One comment at the decision point and a changelog line make the tradeoff explicit. * fix(coding-agent): kill before cleanup in the pre-roster replacement The carve ran recoverUncertainWorkerOperations (interruption marking, orphan reaping) before the identity-gated SIGKILL, so destructive cleanup hit a still-active worker; pre-rebase the kill preceded cleanup inside the old helper. Reordered: recheck + SIGKILL + bounded teardown wait first, cleanup only after the predecessor is confirmed gone/replaced, and an unverifiable survivor parks failed with NO destructive cleanup at all (matching recovery's rule that a possibly-live worker is never cleaned destructively). The carve pin asserts the order and the no-cleanup branch. * fix(coding-agent): never release a registry guard a successor reclaimed proper-lockfile verified: compromise detection is timer-driven (mtime updates), and release() removes the lockfile unconditionally when the steal has not been detected yet — so a caller resuming from a stall past the stale threshold could delete the successor'"'"'s lock. The guard directory'"'"'s inode is the ownership identity (a steal is rmdir+mkdir): it is captured at acquisition and checked synchronously where the timer cannot run — before returning an action'"'"'s result and before release. A stolen-but-undetected guard is left to its own updater, which notices the foreign mtime and cleans itself. A truly synchronous stall still cannot be preempted mid-action; that limitation is stated at the guard. --------- Co-authored-by: Sebastian <sebastian@primeintellect.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Linear: ENG-5827
Summary
helloas unresponsive, give it the full startup window, and revalidate stale observations before shutdown.proper-lockfilecall site inpackages/coding-agent.recoveringand retry asynchronously.No daemon wire shape changes are included. Windows named-pipe behavior is unchanged.
Prior-art audit
This implements only behavior still missing from the closed, unmerged PRs:
main.main, but probe timeouts could still replace a verified-live worker.I also checked #1523, #1756, #1895, #1897, #1900, #1909, #1926, #1123, and #1236. Only #1756 is merged. Sebastian's stack does not implement the startup, catalog, or lock-loss fixes here. #1897 adds PID-reuse protection and roster recovery state in the same supervisor functions, so it will need a deliberate conflict resolution, but it does not preserve live workers after probe timeouts or gate destructive cleanup on catalog durability. #1895, #1900, and #1909 do not duplicate these behaviors.
Invariants
proper-lockfilecompromise callback records state and never throws through the refresh callback.hellois never classified as stale.Validation
npm run check: passed.test/daemon-supervisor-process.test.ts: 10 passed, 8 skipped.The process suite was run with inherited
PRIME_AGENT_INTERNAL_DAEMON_*variables removed because this development shell is itself a daemon worker.Note
High Risk
Changes core daemon lifecycle, distributed locking, and worker recovery paths where mistaken kills or socket unlink could interrupt active sessions; behavior is intentionally more conservative but affects every CLI daemon attach.
Overview
Hardens daemon startup, lock ownership, and live-worker recovery so the CLI and supervisor fail closed instead of replacing or killing processes they cannot safely own.
CLI startup now treats a connected socket with no
daemon_helloas unresponsive (not stale), waits through the startup window, and errors with force-shutdown guidance rather than shutting down a possibly-busy daemon. Stale replacement re-checks the connected daemon’s hello before issuing shutdown and can reuse a peer that finishes starting mid-check.proper-lockfilecompromise is wired through daemon socket leases, supervisor/coordinator registry guards, session leases, auth, settings, and cron jobs: compromised locks abort mutations, skip unsafe socket unlink, and (for the supervisor) fence the server and relinquish ownership.Worker recovery introduces typed probe/auth errors, keeps verified-live or identity-unknown workers in
recoveringwith deferred probes (capped at ~10 rounds), and only SIGKILLs a live process for the identity-verified pre-roster adoption path. Destructive cleanup no longer kills resident workers fromrecoverUncertainWorkerOperations; catalog start and interruption persistence run before orphan reaping.Catalog spawns a dedicated entrypoint (Node/Bun), 30s cold-start timeout, and fails fast if the child exits during startup.
Reviewed by Cursor Bugbot for commit 4659595. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Harden daemon startup and socket lease compromise recovery
DaemonSocketPathLeaseand all subsystem locks (auth, settings, cron, session-lease, update-coordinator) so operations fail fast instead of proceeding or releasing a lock a successor may already ownprobeDaemonVersionnow returnsunresponsivefor connected-but-silent daemons instead ofstale;ensureDaemonRunningwaits through the startup window before surfacing an error, andshutdownStaleDaemonIfNotBusyreturns precise dispositions (current/stopped/busy)DaemonWorkerProbeTimeoutErrorandDaemonWorkerAuthenticationErrortypes, bounds deferred probe rounds toMAX_DEFERRED_RECOVERY_ROUNDS(10), and avoids destructive cleanup or orphan reaping when process identity is uncertainDaemonSupervisorfences all client connections viafenceSupervisorSocketand begins async cleanup; mutating commands re-validate serving state after an idle-eviction fenceDAEMON_CATALOG_START_TIMEOUT_MS), and the spawner resolves.tsentrypoints viatsxor compiled.jsdepending on run moderecoverUncertainWorkerOperationssignature changed (boolean pre-cleanup param removed); in-tree callers and tests in daemon-agent-roster.test.ts and daemon-supervisor-monitor.test.ts are updated.DaemonVersionProbetype now requireshelloforstaleand addsunresponsive— any out-of-tree callers ofprobeDaemonVersionneed to handle the new union member.Macroscope summarized 4659595.