fix(supervisor): require a start time to identify an orphaned process - #665
Conversation
Orphan cleanup accepted a matching process name as identity for state written before start times were recorded. A name is not an identity: another copy of the same program on a recycled PID matches it, so a legacy record could authorize adopting or killing an unrelated process. Require a recorded and a readable current start time. Records that have neither are unverifiable rather than mismatched, so they retain their running state instead of being reset or acted on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 3 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Central YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughDaemon identity checks now rely on kernel process start times. Stop and resource-violation paths refuse signalling when recorded and current identities conflict, while reconciliation preserves running state when identity is unavailable. Tests cover helper semantics and recycled-PID stopping. ChangesPID identity safety
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant Supervisor
participant PROCS
participant DaemonState
Client->>Supervisor: Request daemon stop
Supervisor->>DaemonState: Read recorded start_time
Supervisor->>PROCS: Read current PID start_time
Supervisor->>Supervisor: Evaluate signalling authorization
alt Start times contradict
Supervisor->>DaemonState: Set pid=None and status=Stopped
Supervisor-->>Client: DaemonWasNotRunning
else PID authorized
Supervisor->>PROCS: Signal process group
end
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
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 |
Greptile SummaryTightens orphan identity matching and adds PID start-time checks to process-group signalling paths.
Confidence Score: 3/5This PR is not yet safe to merge because unverifiable retained daemon records can still signal an unrelated process group after PID reuse. The new signalling helper rejects only positively unequal start times; missing identity remains authorized even though orphan reconciliation deliberately preserves those records as running, leaving explicit stop and resource-limit kill paths able to act on an unrelated recycled PID. Files Needing Attention: src/supervisor/mod.rs, src/supervisor/lifecycle.rs, src/supervisor/watchers.rs, src/supervisor/adopt.rs Important Files Changed
Reviews (3): Last reviewed commit: "fix(supervisor): finalize a daemon whose..." | Re-trigger Greptile |
Stopping a daemon signals its whole process group using a PID read from persisted state, with no identity check: if that PID was recycled while the record sat unsupervised, the signal lands on an unrelated process tree. The same applies to a resource-limit kill. Refuse to signal when the recorded and live start times positively disagree, and report the daemon as not running, which it is. An unknown start time on either side still signals as before, so a record written before start times existed cannot become permanently unstoppable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Confirmed and fixed in 9453ac8. What changed. Why it's weaker than What I didn't use. The four other Test. This comment was generated by Claude Code. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9453ac8. Configure here.
Refusing to signal a recycled PID left the record running, so the resource watcher measured the stranger's usage on the next tick and tried to kill it again, every tick. Record the unobserved death through finalize_if_pid, which revalidates ownership inside the state lock: the same terminal state orphan reconciliation reaches, and one that keeps the daemon eligible for retry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Correct, fixed in the latest commit. The refusal branch returned without touching state, so on the next tick the watcher would read the stranger's memory/CPU as the daemon's, refuse to kill again, and repeat indefinitely. It now finalizes through On the second location you flagged: 523 unit tests; supervisor + interval suites 31/31. This comment was generated by Claude Code. |

The hole
Orphan cleanup verifies that a live PID really belongs to the daemon recorded against it before adopting or killing it. Identity is the kernel start time — but when a record had no start time,
process_identity_matchesfell back to comparing the process name:A name is not an identity. It is exactly what a second copy of the same program shares, so a recycled PID running
node,nginx, orpostgresmatches a legacy record for a different daemon of the same program — and the supervisor then adopts it, or kills it underorphan_policy = "kill". That is the failure mode the identity check exists to prevent, left open for precisely the records that predate the check.The fallback was kept in #632 to avoid breaking the ≤2.17.0 upgrade path. It buys very little: the window is one supervisor restart after upgrading, and every record written since then has a start time.
The change
Both start times are required. A missing one on either side means unverifiable, which is treated differently from mismatched:
So a legacy record now stops being a licence to kill: it keeps its running state until the daemon is next started under a version that records identity properly.
Tests
orphan_identity_requires_both_start_timesandorphan_identity_rejects_records_without_a_start_timereplace the three title-fallback cases. 521 unit tests and 26/26supervisor.batspass locally.This PR was generated by Claude Code.
Note
High Risk
Changes process-group kill/stop and orphan adopt/kill decisions on PID reuse; mistakes could leave daemons unstoppable or, before this fix, signal the wrong tree.
Overview
Orphan identity no longer falls back to comparing process names when
start_timeis missing.process_identity_matchesonly succeeds when both recorded and live kernel start times are present and equal; legacy or unreadable identity is treated as unverifiable (keep running, log a warning) rather than mistaken for a mismatch (reset state). Startup orphan cleanup and interval reconciliation share that behavior.User-initiated signalling adds
signalling_pid_is_authorized, which is weaker than orphan matching: stop and resource-violation kills are skipped only when start times contradict each other (proven PID reuse). Missing start time still allows stop so old records do not become unstoppable. On contradiction,stopclears the daemon as stopped withoutkillpg; the resource watcher finalizes the record instead of killing a stranger’s process group.Tests cover the new identity rules and a bats case where
pitchfork stopmust not tear down an unrelated process group that recycled the recorded PID.Reviewed by Cursor Bugbot for commit acb18ee. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
Bug Fixes
Tests