Skip to content

fix(cli): verify process identity before killing tracked watch PIDs - #1665

Merged
bradygaster merged 2 commits into
github:devfrom
omercangumus:omercangumus/1641-pid-tracker-identity
Aug 23, 2026
Merged

bradygaster merged 2 commits into
github:devfrom
omercangumus:omercangumus/1641-pid-tracker-identity

Conversation

@omercangumus

Copy link
Copy Markdown
Contributor

What

squad watch's PidTracker now cross-checks a live process's real OS start time against the tracked spawnedAt before killing it in cleanup()/cleanupStale(), instead of trusting a bare PID number.

Why

Fixes #1641

isAlive() is process.kill(pid, 0) and killTree() kills by PID alone — name/spawnedAt were only ever used in the post-kill log line, never checked before the kill. If watch dies without cleaning up .watch-pids and the OS reassigns that PID to something unrelated before the next cleanupStale() run, the tracker kills whatever now owns that number. Repro is in the issue: a .watch-pids entry claiming a month-old spawn gets killed against a process that actually started 90 seconds ago, no contradiction check anywhere in the path.

How

  • getProcessStartTime(pid): looks up the OS's real start time for a PID — wmic process where ProcessId=X get CreationDate on Windows (parses the CIM_DATETIME UTC-offset field properly, not just the local wall-clock digits), ps -o lstart= -p X elsewhere. Returns null if it can't be determined.
  • matchesTrackedIdentity(proc): compares that against proc.spawnedAt with a 5-minute tolerance (spawn/scheduling delay, not the minutes-to-months drift a real PID reuse produces). Fails open (returns true) when the start time can't be determined, so normal cleanup isn't blocked when wmic/ps aren't available.
  • cleanup() and cleanupStale(): both call the check right before killTree(); on mismatch they log a warning and skip the kill instead of proceeding.

Testing

Added a identity check (#1641) block to the existing test/cli/watch-pid-tracker.test.ts suite, same mock style as the rest of the file (execFileSync mocked at module level, process.kill spied per-test):

  • skips the kill when the OS start time contradicts the tracked record (recreates the issue's exact repro numbers)
  • still kills when the start times line up within tolerance
  • still kills when the start time can't be determined at all (fail-open, matches prior behavior for anyone without wmic/ps)
  • parses a real Windows wmic CreationDate response and a real POSIX ps -o lstart= response into the correct UTC instant
npx vitest run test/cli/watch-pid-tracker.test.ts
 Test Files  1 passed (1)
      Tests  16 passed (16)

Ran the broader watch suite too — test/cli/watch.test.ts hit its known cold dynamic-import 5s timeout (3 tests), confirmed pre-existing by rerunning the same file with --testTimeout=20000, all 6 pass. Not caused by this change; same quirk documented on prior PRs against this repo.

cd packages/squad-sdk && npm run build then cd packages/squad-cli && npm run build (both package-local, no root prebuild churn) — clean, tsc --noEmit -p packages/squad-cli/tsconfig.json clean. eslint on both changed files: 0 errors, 12 warnings, all n/no-sync/no-console on the same categories the file already triggered before this change (execFileSync, console.log) — no new warning categories.

Diff hygiene: git diff vs git diff -w identical, git diff --check clean — hit the usual CRLF drift on this box (working copy silently went CRLF on both edited files despite LF-committed blobs), normalized back to LF and reverified against git show HEAD:<path> before staging.


⚠️ Quick Check

  • Changeset added: .changeset/1641-pid-tracker-identity-check.md (patch @bradygaster/squad-cli)

PR Readiness Checklist

Branch & Commit

  • Branch created from dev (upstream, fresh fetch)
  • Branch is up to date with dev
  • Verified diff contains only intended changes (1 source + 1 test file + changeset)
  • PR is not in draft mode
  • Commit history is clean (single commit)

Build & Test

  • npm run build (per-package) passes
  • npm test — targeted suite 16/16; broader watch suite checked, only the known pre-existing cold-start timeout
  • tsc --noEmit clean
  • eslint — 0 errors, pre-existing warning categories only

Changeset

  • Changeset added (cli patch)

Docs

  • N/A — internal behavior fix, no public CLI surface changed

Exports

  • N/A — no new modules or exports

Breaking Changes

None. matchesTrackedIdentity is additive and private; the only externally visible change is that a kill can now be skipped (with a logged warning) in the specific case where the live process contradicts the tracked record.

Waivers

None.

Fixes github#1641

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Aug 9, 2026 •

Copy link
Copy Markdown
Contributor

🛫 PR Readiness Check

ℹ️ This comment updates on each push. Last checked: commit a9323df

PR Scope: 📦🔧 Mixed (product + infrastructure)

⚠️ 3 item(s) to address before review

Status Check Details
❌ Single commit 2 commits — consider squashing before review
✅ Not in draft Ready for review
✅ Branch up to date Up to date with dev
❌ Copilot review No Copilot review yet — it may still be processing
✅ Changeset present Changeset file found
✅ Scope clean No .squad/ or docs/proposals/ files
✅ No merge conflicts No merge conflicts
✅ Copilot threads resolved No Copilot review threads
❌ CI passing 9 check(s) still running

Files Changed (3 files, +176 −0)

File +/−
.changeset/1641-pid-tracker-identity-check.md +5 −0
packages/squad-cli/src/cli/commands/watch/pid-tracker.ts +62 −0
test/cli/watch-pid-tracker.test.ts +109 −0

Total: +176 −0


This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

🟡 Impact Analysis — PR #1665

Risk tier: 🟡 MEDIUM

📊 Summary

Metric Count
Files changed 3
Files added 1
Files modified 2
Files deleted 0
Modules touched 3

🎯 Risk Factors

  • 3 files changed (≤5 → LOW)
  • 3 modules touched (2-4 → MEDIUM)

📦 Modules Affected

root (1 file)
  • .changeset/1641-pid-tracker-identity-check.md
squad-cli (1 file)
  • packages/squad-cli/src/cli/commands/watch/pid-tracker.ts
tests (1 file)
  • test/cli/watch-pid-tracker.test.ts

This report is generated automatically for every PR. See #733 for details.

@omercangumus

Copy link
Copy Markdown
Contributor Author

the one CI test failure is pre-existing, unrelated to this PR — test/cli/upgrade.test.ts > ensureCastingDefaults scaffolds registry.json, policy.json, history.json expects casting_policy_version: '1.1' but the template's on 1.2 now (bumped in #1643). that's #1661, already filed. this branch's diff is 3 files (pid-tracker.ts, its test, changeset) — nothing touches casting templates, and the version bump predates this branch on dev.

@github-actions

Copy link
Copy Markdown
Contributor

👋 Friendly nudge — this PR has had no activity for 8 days.

What needs attention:

  • 🔴 1 CI check(s) failing: test. Fix these first.
  • 👀 No approving reviews yet. Request a review from a teammate.
  • ⬇️ 83 commits behind dev. Rebase to pick up latest changes.

If this PR is abandoned, please close it. If it's blocked on something external, leave a comment so the team knows.
This is an automated check that runs on weekdays. It won't nudge the same PR more than once per week.

@bradygaster
bradygaster merged commit 9ede019 into github:dev Aug 23, 2026
16 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.

bug: watch's pid-tracker kills any process matching a tracked PID, no identity check

2 participants