Skip to content

fix(bg): revalidate process identity before signals - #1937

Merged
kevincodex1 merged 3 commits into
Twigpine:mainfrom
chioarub:fix/bg-presignal-identity
Jul 14, 2026
Merged

kevincodex1 merged 3 commits into
Twigpine:mainfrom
chioarub:fix/bg-presignal-identity

Conversation

@chioarub

@chioarub chioarub commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Revalidate a background session PID immediately before SIGTERM and SIGKILL.
  • Refuse to signal or mark the session killed when the live process identity mismatches or cannot be read.

Implementation

  • Expose a four-state session identity result backed by strict command-token matching and tri-state process liveness.
  • Keep generic process-tree cleanup reusable while routing registered session termination through a session-bound verifier.
  • Recheck liveness after command lookup so natural exits succeed and access-denied transitions fail closed.

Tests

  • Cover matching, mismatched, unreadable, naturally exited, escalation, stale verifier, refreshed stale/unknown, and access-denied races with deterministic call-order assertions.
  • Validate CLI routing, type checking, and production builds.

Risk

  • The signal boundary still has an unavoidable narrow race after successful verification and before kernel delivery.
  • Generic cleanup for newly spawned unregistered children remains liveness-only by design.

Refs #1770

Summary by CodeRabbit

  • Bug Fixes

    • Improved safety for terminating background sessions by verifying the process identity before sending termination signals.
    • Avoid signaling when the process is no longer running or when identity/command verification is mismatched or unreadable, including during SIGTERM grace and SIGKILL escalation.
    • Refined background-session status reporting to better distinguish matching processes (“running”) from unreadable ones (“unknown”).
  • Tests

    • Expanded coverage for identity classification, liveness/command probing edge cases, and escalation/termination safety behaviors.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

Changes

Background session process safety

Layer / File(s) Summary
Process identity verification
src/cli/bgRegistry.ts, src/cli/bgRegistry.test.ts
Adds exported liveness and identity types, two-phase process verification, unreadable/error classification, and status refresh mapping based on verification state.
Verified termination flow
src/cli/bg.ts, src/cli/bg.test.ts
Revalidates session identity before SIGTERM and SIGKILL, handles terminal and naturally exited processes, updates kill handling, and prevents signaling on mismatches or unreadable identities.
Termination safety validation
src/cli/bg.test.ts
Covers PID reuse, stale verifier results, verifier exceptions, liveness failures, process exits, and escalation ordering.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

Suggested labels: bug

🚥 Pre-merge checks | ✅ 7
✅ Passed checks (7 passed)
Check name Status Explanation
Title check ✅ Passed Concise and scoped, and it accurately reflects the main change of revalidating process identity before SIGTERM/SIGKILL.
Description check ✅ Passed Mostly follows the template with clear Summary, Tests, and Risk sections, though Impact and Notes are missing.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Risk Surface Disclosed ✅ Passed PASS: The PR description’s Risk section explicitly calls out the narrow signal-delivery race and the liveness-only generic cleanup boundary, with no blocker introduced.
No Hidden Policy Change ✅ Passed Policy-affecting behavior is explicit here: probe failures are sanitized to unreadable refusals, with no unrelated routing/default/telemetry changes hidden in cleanup.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 10, 2026

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

3. Findings

High

H1 — Live-but-unreadable session becomes permanently un-killable (availability regression).
killBackgroundSession (bg.ts:859-867) calls authorizeBackgroundSessionSignal, which throws on unreadable/mismatch (bg.ts:845-850). Because the throw precedes markKilled, a session whose live identity cannot be confirmed is never killed and the operator is told to "re-run openclaude ps and retry." But the identity can stay unreadable indefinitely:

  • getProcessCommand (genericProcessUtils.ts:94-107) collapses every error to null; verifyBackgroundSessionProcessIdentity then maps command == null to 'unreadable' (bgRegistry.ts:611-613). Any failure to read the command line — Windows powershell.exe Get-CimInstance denied/missing, ps denied (e.g., another user's process or a restricted container/PID namespace), or proc momentarily unavailable — yields unreadable.
  • getBackgroundSessionProcessLiveness (bgRegistry.ts:630-636) classifies any non-ESRCH process.kill(pid,0) error as 'unreadable', which also refuses the kill.

The prior killHandler used isProcessRunning(pid) (old genericProcessUtils.ts:20-28), which caught all errors as "not running" and killed the process. The new code is stricter and can leave a legitimately-owned, still-alive session un-killable. This is most likely on Windows (powershell/elevation mismatch) but identical failure modes exist on Unix (permission-denied ps, PID namespaces).

Recommended fix: distinguish "process exists but its command line is unreadable" from identity mismatch. For a live PID (liveness === 'alive') whose command line merely can't be introspected, prefer a best-effort kill (possibly with a warning) rather than a hard refuse — keep 'mismatch' as the strict fail-closed case. At minimum, allow signaling when liveness is alive even if the command is unreadable.

Medium

M1 — Injected identity options are not wrapped, bypassing fail-closed and leaking raw text.
getBackgroundSessionProcessLiveness wraps signalProcess in try/catch (bgRegistry.ts:631-636), but three equivalent injection points are not wrapped:

  • options.verifySessionIdentity in verifySelectedBackgroundSessionIdentity (bg.ts:824-826) — a throwing verifier propagates raw, never becoming the controlled unverifiedProcessError.
  • options.getProcessCommand in verifyBackgroundSessionProcessIdentity (bgRegistry.ts:607-608) — called bare (no try/catch), unlike the wrapped signalProcess.
  • options.isProcessAlive in getBackgroundSessionProcessLiveness (bgRegistry.ts:625-627) — called bare.

If any injected probe throws, the 'unreadable' → refuse classification is bypassed and the raw (potentially sensitive) error text escapes. The production default path is safe (the default getProcessCommand/isProcessRunning are internally guarded), but the public option surface advertises exactly this misuse and contradicts the PR's stated "fail closed on unreadable" guarantee (wave1-failure A/B/C, wave2-contract F3).

Recommended fix: wrap verifySessionIdentity, readCommand, and options.isProcessAlive call sites in try/catch that map exceptions to 'unreadable' (mirroring the signalProcess guard).

M2 — Identity is argv-only, a weak/forgeable authenticator.
commandLineMatchesBackgroundSession (bgRegistry.ts:578-589) is the sole discriminator and matches only contiguous whole-token runs of the stored session.command (or the session UUID). This correctly defeats substring and interleaved-token collisions (#1770), but:

  • A PID reused by any process with identical argv (another openclaude session launched with the same prompt/flags, or an attacker re-exec'ing the same image) yields a confident matches → a wrong-but-"justified" kill (wave1-skeptic F2).
  • The contiguous-run matcher returns true for a stored argv that is a prefix of an unrelated line (bgRegistry.ts:565-575); a crafted leading argv could in theory produce a false matches (wave1-skeptic F5, Low likelihood).

The PR's own description concedes the residual kernel-level TOCTOU (verification and signaling are two separate syscalls). argv is attacker-controllable and cannot be the only anchor.

Recommended fix (defense in depth, not blocking): capture a harder-to-spoof property at spawn time (process start time from /proc/<pid>/stat, or a per-session nonce on argv) and additionally target the process group (setpgid + kill(-pgid, sig)) so a reused PID in a different group is not signalled.

M3 — Test coverage/robustness gaps on the security-critical path.

  • killHandler (the actual CLI entry point, bg.ts:917-931) is exercised only indirectly via the lower-level killBackgroundSession; there is no end-to-end killHandler test (wave2-claims F1).
  • The real errno classification (ESRCH→not-running, EPERM→unreadable) is only tested via injected mock errors, never via a real NodeJS.ErrnoException (wave2-claims F2).
  • bgRegistry.test.ts:993-1003 asserts aliveChecks === 2, coupling the test to the double-liveness-probe implementation detail; a legitimate single-probe refactor would keep behavior but break the count (wave2-claims F3, Medium).
  • Several refusal assertions are substring-only (toThrow('refused to signal an unverified process')) and one uses calls.slice(0,2) instead of an exact-array check (bg.test.ts:552, 672, 693, 722, 817; wave2-claims F4/F5). None let a security bug through given the suite as a whole, but they reduce regression sensitivity.
  • No win32 test asserts missing-pid → 'not-running' (see H1/M1 cross-platform risk).

Low

L1 — Latent infinite-recursion footgun (currently unreachable). The isProcessAlive built in terminateBackgroundSessionProcessTree (bg.ts:806) is pid => getLiveness(pid) !== 'not-running', and getLiveness → getBackgroundSessionProcessLiveness(pid, options), which consults options.isProcessAlive if present (bgRegistry.ts:625-627). Today the verification closures capture the original options (without the recursive closure), so it is unreachable — but a future refactor that spreads the augmented options into verification would deadlock the CLI (wave2-failure F1). Fix: have getBackgroundSessionProcessLiveness ignore options.isProcessAlive, or assert it is never forwarded.

L2 — command == null vs ''. bgRegistry.ts:611 treats only null/undefined as unreadable. If an injected getProcessCommand returned '', commandLineMatchesBackgroundSession('', session) returns false → mismatch (not unreadable). The production getProcessCommand can't return '' (result ? result.trim() : null), so this is unreachable in practice; hardening: if (command == null || command === '') (wave2-skeptic F5).

L3 — mismatch conflated into stale status. refreshBackgroundSessionStatuses maps both not-running and mismatch to stale (bgRegistry.ts:490). Because every actual signal goes through a fresh authorizeBackgroundSessionSignal that throws on mismatch, this is display-only and safe, but a live-but-wrong-PID process shows as stale rather than something more diagnostic (wave1-state F3).

L4 — verifyBeforeSignal declared return type omits that it throws. The signature Promise<'matches' | 'not-running'> (bg.ts:728-732) doesn't express the mismatch/unreadable → throw path. TypeScript-legal and all callers handle throws, but the type is imprecise (wave2-failure F2, info).

L5 — Double liveness probe = 2 kill(pid,0) syscalls per identity check per running session on every status refresh (bgRegistry.ts:604 + :609). Intended and fail-safe (it is what makes natural-exit-during-lookup return not-running), but doubles syscall overhead; acceptable (wave2-skeptic F1).


@chioarub

Copy link
Copy Markdown
Contributor Author

Follow-up to review 4681028808:

  • H1 not applied: this PR intentionally fails closed when a live process identity is unreadable; authorizing a best-effort signal would violate that safety contract. The prior path also did not signal when identity was unknown and liveness was confirmed.
  • M1 fixed in 40815f93: throwing injected liveness, command, and session-identity probes now map to sanitized unreadable refusals without exposing callback text.
  • M2 deferred: durable identity markers and process-group redesign remain the separate defense-in-depth follow-up tracked by Harden background process identity checks before killing sessions #1770.
  • M3 partially addressed: the pre-SIGTERM assertion now checks the complete call sequence. Existing routing coverage plus lower-level behavioral tests cover the CLI delegation and destructive path; the errno fixture uses the structural NodeJS error shape, and missing-PID classification is platform-independent.
  • L1 not changed: the recursive closure is not forwarded into the captured original options; this remains an unreachable future-refactor concern.
  • L2 fixed in 40815f93: empty and whitespace-only command output is now unreadable.
  • L3 not changed: mismatch-to-stale display mapping intentionally preserves existing refresh behavior; every destructive signal still revalidates.
  • L4 not changed: the Promise type describes fulfillment values; refusal is represented by rejection.
  • L5 accepted: the second liveness probe is intentional proof against natural exit and access-denied races.

Validation: focused background-session tests, typecheck, and build pass.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/cli/bgRegistry.test.ts (1)

1005-1016: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test doesn't actually assert getProcessCommand was skipped.

The comment/intent ("command lookup must not run without confirmed liveness") isn't enforced by an assertion — the test only checks result.state === 'unreadable', which would also pass if getProcessCommand ran and threw (since that's caught and also maps to 'unreadable'). Track a call count like the "exit during command lookup" test does at line 993-1002, to actually prove the short-circuit.

✅ Suggested strengthening
   it('treats an access-denied liveness probe as unreadable', () => {
+    let commandCalls = 0
     const result = verifyBackgroundSessionProcessIdentity(session, {
       signalProcess: () => {
         throw Object.assign(new Error('access denied'), { code: 'EPERM' })
       },
       getProcessCommand: () => {
+        commandCalls++
         throw new Error('command lookup must not run without confirmed liveness')
       },
     })

     expect(result.state).toBe('unreadable')
+    expect(commandCalls).toBe(0)
   })
🤖 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 `@src/cli/bgRegistry.test.ts` around lines 1005 - 1016, Strengthen the
access-denied test around verifyBackgroundSessionProcessIdentity by tracking
getProcessCommand invocations with a call counter, then assert the counter
remains zero alongside the existing unreadable-state assertion. Preserve the
current throwing stub so the test verifies command lookup is skipped after the
EPERM liveness failure.

Source: Path instructions

src/cli/bgRegistry.ts (1)

481-490: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Ternary chain over identity state isn't exhaustive at the type level.

processState === 'matches' ? 'running' : processState === 'unreadable' ? 'unknown' : 'stale' silently folds both 'not-running' and 'mismatch' into 'stale', and any future state added to BackgroundSessionProcessIdentity['state'] would fall into 'stale' without a compiler error. A switch with exhaustive cases (or a lookup map keyed by the full union) would catch missing mappings at compile time.

♻️ Exhaustive mapping alternative
-    const nextStatus: BackgroundSessionStatus =
-      processState === 'matches'
-        ? 'running'
-        : processState === 'unreadable'
-          ? 'unknown'
-          : 'stale'
+    const nextStatus: BackgroundSessionStatus = ((): BackgroundSessionStatus => {
+      switch (processState) {
+        case 'matches':
+          return 'running'
+        case 'unreadable':
+          return 'unknown'
+        case 'not-running':
+        case 'mismatch':
+          return 'stale'
+      }
+    })()
🤖 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 `@src/cli/bgRegistry.ts` around lines 481 - 490, Replace the nested ternary
assigning nextStatus with an exhaustive switch or full-union lookup over
processState in the surrounding background session status flow. Explicitly map
every current BackgroundSessionProcessIdentity state, including 'matches',
'unreadable', 'not-running', and 'mismatch', while preserving their intended
statuses; ensure future union members produce a compile-time error rather than
defaulting to 'stale'.
src/cli/bg.ts (1)

807-816: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Drop the unreachable pid guard. terminateBackgroundProcessTree() is only invoked here with session.pid, so this callback never sees a descendant PID. verifySelectedBackgroundSessionIdentity() already covers the PID match, so the extra branch can be removed.

🤖 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 `@src/cli/bg.ts` around lines 807 - 816, Remove the unreachable pid comparison
and its unverifiedProcessError branch from the verifyBeforeSignal callback;
retain the verifySelectedBackgroundSessionIdentity(session, options) call and
return authorizeBackgroundSessionSignal(session, identity).
🤖 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.

Outside diff comments:
In `@src/cli/bg.ts`:
- Around line 807-816: Remove the unreachable pid comparison and its
unverifiedProcessError branch from the verifyBeforeSignal callback; retain the
verifySelectedBackgroundSessionIdentity(session, options) call and return
authorizeBackgroundSessionSignal(session, identity).

In `@src/cli/bgRegistry.test.ts`:
- Around line 1005-1016: Strengthen the access-denied test around
verifyBackgroundSessionProcessIdentity by tracking getProcessCommand invocations
with a call counter, then assert the counter remains zero alongside the existing
unreadable-state assertion. Preserve the current throwing stub so the test
verifies command lookup is skipped after the EPERM liveness failure.

In `@src/cli/bgRegistry.ts`:
- Around line 481-490: Replace the nested ternary assigning nextStatus with an
exhaustive switch or full-union lookup over processState in the surrounding
background session status flow. Explicitly map every current
BackgroundSessionProcessIdentity state, including 'matches', 'unreadable',
'not-running', and 'mismatch', while preserving their intended statuses; ensure
future union members produce a compile-time error rather than defaulting to
'stale'.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f5535b99-acb8-433e-94aa-465c9d30b50d

📥 Commits

Reviewing files that changed from the base of the PR and between b3a0f40 and 40815f9.

📒 Files selected for processing (4)
  • src/cli/bg.test.ts
  • src/cli/bg.ts
  • src/cli/bgRegistry.test.ts
  • src/cli/bgRegistry.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (3)
  • GitHub Check: smoke-and-tests (22)
  • GitHub Check: typecheck
  • GitHub Check: smoke-and-tests (24.11.x)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

TypeScript code in this repository must use strict mode and ESM imports.

Files:

  • src/cli/bg.ts
  • src/cli/bgRegistry.test.ts
  • src/cli/bgRegistry.ts
  • src/cli/bg.test.ts
**

⚙️ CodeRabbit configuration file

**: # AGENTS.md - AI Agent Coding Guide

This guide is for AI coding agents working in the OpenClaude repository. Read it before changing code, and also follow CONTRIBUTING.md for contributor policy, PR expectations, review follow-up, and project scope.

Project Snapshot

OpenClaude is a coding-agent CLI for cloud and local model providers. It supports OpenAI-compatible APIs, Anthropic, Gemini, DeepSeek, Ollama, MCP, local backends, slash commands, tools, agents, and a React/Ink terminal UI.

The installed CLI runs on Node.js >=22.0.0. Bun is used for source builds, scripts, dependency management, and tests.

Work Style

  • Keep changes focused on one problem.
  • Prefer existing patterns in the file or nearby module.
  • Avoid unrelated formatting, renames, dependency changes, or broad rewrites.
  • Add or update tests when behavior changes.
  • Update docs when setup, commands, provider behavior, or user-facing behavior changes.
  • For new features, larger refactors, dependencies, or runtime changes, follow the issue-first guidance in CONTRIBUTING.md.

Stack And Conventions

  • TypeScript with strict mode and ESM imports.
  • React + Ink for terminal UI.
  • Bun lockfile and Bun scripts for development workflows.
  • Node runtime for the built CLI.

Common libraries and patterns:

  • chalk for terminal color.
  • commander for CLI argument parsing.
  • execa for child processes.
  • Existing service, provider, settings, permission, and UI patterns over new abstractions.

Repository Map

  • src/commands/ - slash and CLI command implementations.
  • src/components/ - React/Ink UI components.
  • src/services/ - API, MCP, OAuth, wiki, voice, and other service integrations.
  • src/tools/ - tool implementations.
  • src/utils/ - shared utilities.
  • src/integrations/ - provider and model integration metadata.
  • src/entrypoints/ - CLI, MCP, SDK, and generated public types.
  • src/tasks/ - local, remote, workflow, and monitor tas...

Files:

  • src/cli/bg.ts
  • src/cli/bgRegistry.test.ts
  • src/cli/bgRegistry.ts
  • src/cli/bg.test.ts
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the OpenClaude maintainer review rubric from AGENTS.md. Review the current diff, not stale discussion context. Separate real blockers from suggestions. Do not request changes for vague style churn. Treat approval as merge-ready from CodeRabbit's side, pending required human review and GitHub Checks. If checks are failing or unavailable, say so clearly instead of implying the PR is fully ready.

Files:

  • src/cli/bg.ts
  • src/cli/bgRegistry.test.ts
  • src/cli/bgRegistry.ts
  • src/cli/bg.test.ts
{src/**/*.test.ts,src/**/*.test.tsx,tests/**,scripts/**/*.test.ts,vscode-extension/**/*.test.js}

⚙️ CodeRabbit configuration file

{src/**/*.test.ts,src/**/*.test.tsx,tests/**,scripts/**/*.test.ts,vscode-extension/**/*.test.js}: Review tests for meaningful coverage of the changed behavior, isolation of global/env/config state, async cleanup, fake timers, provider profile leaks, and Windows-compatible assumptions. Block when risky runtime changes lack focused regression coverage or tests assert implementation details while missing the user-visible behavior.

Files:

  • src/cli/bgRegistry.test.ts
  • src/cli/bg.test.ts
🪛 ast-grep (0.44.1)
src/cli/bg.ts

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from 'node:child_process'
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🔇 Additional comments (10)
src/cli/bg.ts (2)

820-847: LGTM! Exceptions from verifySessionIdentity are correctly sanitized into a generic refusal without leaking callback error details, and the reported identity is cross-checked against the expected session/pid before being trusted.


863-878: 🎯 Functional Correctness

No issue: authorizeBackgroundSessionSignal already throws on 'unreadable', so killBackgroundSession cannot fall through to markKilled on that path.

			> Likely an incorrect or invalid review comment.
src/cli/bgRegistry.ts (2)

591-627: LGTM! The liveness → command-read → re-check-liveness sequence correctly closes the TOCTOU window between confirming the process is alive and reading/matching its command line, and exceptions from the injected command reader are safely downgraded to 'unreadable'/latest liveness.


629-649: LGTM! Liveness classification correctly distinguishes ESRCH (not-running) from other errno cases (unreadable), and injected isProcessAlive throws are contained.

src/cli/bgRegistry.test.ts (1)

961-1045: LGTM! Good coverage of the identity state matrix (not-running/matches/mismatch/unreadable), exception sanitization for both liveness and command probes, and whitespace/empty command handling.

src/cli/bg.test.ts (5)

522-532: LGTM!


558-588: LGTM! Confirms fail-closed behavior on identity mismatch (no signal, no mark-killed) and that sensitive session data isn't leaked into the refusal message.


771-800: LGTM! Sequencing assertion (verify → SIGTERM → sleep → verify → SIGKILL → sleep) is a solid, precise regression guard for the pre-escalation revalidation behavior.


802-820: LGTM! Directly exercises terminateBackgroundSessionProcessTree's rejection of stale verifier results for another session/pid, with no signaling.


823-847: LGTM! Confirms killBackgroundSession sanitizes a throwing verifier without leaking details and performs no signal/mark side effects, consistent with the sanitization logic in verifySelectedBackgroundSessionIdentity.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 13, 2026

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I found issues that need to be addressed before this is ready.

Findings

  • [P1] Do not re-signal terminal background-session records
    src/cli/bg.ts:869
    killBackgroundSession now verifies and terminates every resolved record, but refreshBackgroundSessionStatuses deliberately leaves terminal records (stale, killed, exited, and failed) untouched. Consequently, after an old terminal record's PID is reused by a later OpenClaude invocation with the same session ID or stored command, openclaude kill <old-id> can classify that new process as a match and send it SIGTERM/SIGKILL. The previous handler only signaled running records, so this newly turns historical metadata into a destructive target. Preserve the terminal-status gate before verification/signaling (while keeping any desired idempotent metadata update), and add a PID-reuse regression test for a terminal record.

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the contribution. I do not see any actionable issues from my review.

@kevincodex1 LGTM

@kevincodex1
kevincodex1 merged commit af0885d into Twigpine:main Jul 14, 2026
5 checks passed
@chioarub
chioarub deleted the fix/bg-presignal-identity branch July 14, 2026 15:37
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.

3 participants