Skip to content

feat: interceptor diagnose — debugging snapshot for agent diagnosis#105

Merged
ronaldeddings merged 12 commits into
Hacker-Valley-Media:mainfrom
trillium:feat/diagnose-command
Jul 3, 2026
Merged

feat: interceptor diagnose — debugging snapshot for agent diagnosis#105
ronaldeddings merged 12 commits into
Hacker-Valley-Media:mainfrom
trillium:feat/diagnose-command

Conversation

@trillium

@trillium trillium commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

feat: interceptor diagnose — debugging snapshot for agent diagnosis

Hey there, I ran into a problem where 2 interceptor processes were running and went down a troubleshooting rabbit hole 🐇🕳️. Thought this could potentially happent o someone else.

What

Adds interceptor diagnose — a single command that surfaces everything needed to understand why Interceptor isn't working, including a new lock file mechanism and binary mismatch detection that catches the specific failure mode that started this rabbit hole.

The incident this solves

The root cause was a manifest/binary split: the NMH manifest pointed to ~/Projects/interceptor/daemon/interceptor-daemon (dev build) but the running daemon on the socket was /opt/homebrew/bin/interceptor-daemon (Homebrew install). Chrome spawned the manifest binary when the extension connected; the CLI talked to the socket daemon. Two different processes, zero visibility into each other — interceptor tabs timed out for 15 seconds with no useful signal about why.

interceptor diagnose now catches this immediately:

daemon:    running  (pid 12345, /opt/homebrew/bin/interceptor-daemon)
⚠ binary mismatch (chrome):
    socket daemon: /opt/homebrew/bin/interceptor-daemon
    NMH manifest:  /Users/trillium/Projects/interceptor/daemon/interceptor-daemon
    Chrome will spawn the manifest binary; CLI talks to the socket binary.
    Fix: run 'interceptor init' or update the NMH manifest to match.
extension: disconnected  (extension not responding)
monitor:   no sessions

Why diagnose instead of status

status is a pre-flight check — it tells you if the daemon is running and the bridge is installed. diagnose is a post-failure tool — it tells you what's actually connected right now and why commands might be failing. They answer different questions.

Full output (healthy single-browser setup)

daemon:    running  (pid 12345, /opt/homebrew/bin/interceptor-daemon)
extension: connected
tab 3:     https://example.com  "Example Domain"
elements:  24 interactive
monitor:   no sessions

Dual-browser setup (Chrome + Brave):

daemon:    running  (pid 12345, /opt/homebrew/bin/interceptor-daemon)
context chrome-ctx-1:
  extension: connected
  tab 3:     https://example.com  "Example Domain"
  elements:  24 interactive
context brave-ctx-2:
  extension: disconnected  (extension not responding)
  tab:       no active interceptor-group tab
monitor:   no sessions

Daemon not running:

daemon:    not running  — open Chrome with the Interceptor extension, then run 'interceptor init'
monitor:   no sessions

--json available for all of the above.

Changes

New: interceptor diagnose (cli/commands/diagnose.ts)

  • Reads local daemon status + lock file (no daemon needed for this part)
  • Compares lock file execPath against every installed NMH manifest — flags mismatches
  • If daemon is running: enumerates all browser contexts via contexts, probes each in parallel (2 s cap per probe)
  • Reads monitor session state from disk
  • --context <id> to scope to a single context; --json for structured output

New: lock file (daemon/lifecycle.ts, shared/platform.ts, daemon/index.ts)

  • Daemon writes /tmp/interceptor.lock on startup: { pid, version, execPath, startedAt, socketPath, wsPort, mode }
  • Cleared on SIGINT/SIGTERM/SIGHUP
  • checkLockFileDuplicate() exits early with a clear error when a second live daemon is detected at startup — prevents the silent fork
  • LOCK_PATH exported from shared/platform.ts, overridable via $INTERCEPTOR_LOCK_PATH

Updated: cli/index.ts, cli/help.ts

  • diagnose wired into routing, added to NO_DAEMON set (never auto-spawns)
  • Help entries for diagnose, diagnose --context <id>, diagnose --json

Notes for reviewer

  • Lock file execPath uses process.execPath — the actual binary path, not argv[0], so symlinks resolve correctly
  • Binary mismatch check is macOS-only paths currently (Chrome + Brave NMH dirs); Windows path can follow
  • Pre-existing TS errors in extension/src/inject-net.ts and test/screenshot-minimized-preflight.test.ts are not introduced by this PR

Summary by CodeRabbit

  • New Features
    • Added an interceptor diagnose command to inspect daemon state, connected contexts, browser binary mismatches, and monitor sessions.
    • Supports --json output and --context <id> scoping.
  • Bug Fixes
    • Improved detection of browser NMH manifest locations and mismatch warnings.
    • Enhanced shutdown cleanup to remove persisted lock state reliably.
  • Documentation
    • Updated CLI help and command listings for the new diagnostic workflow.

Add 'interceptor diagnose' — a new command that collapses the 4-5
follow-up calls an agent normally issues after a failure into one.

Output:
  daemon:    running  (pid 12345)
  extension: connected
  tab 3:     https://example.com  "Example Domain"
  elements:  24 interactive
  monitor:   no sessions

Works without a daemon (reports local state), surfaces richer context
when daemon + extension are reachable. Probes are capped at 2 s each
and run in parallel so the command stays fast even on a heavy page.

--json emits a structured DiagnoseSnapshot for programmatic use.
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f8b19783-851d-4ea0-9cc1-f04d732557b9

📥 Commits

Reviewing files that changed from the base of the PR and between d38c248 and 8b022ae.

📒 Files selected for processing (1)
  • test/diagnose-lockfile.test.ts

📝 Walkthrough

Walkthrough

Adds a diagnose command that collects daemon, lock-file, browser-manifest, context, and monitor-session state, then prints JSON or formatted diagnostics. It also adds daemon lock-file persistence and exposes the lock path through platform configuration.

Changes

Diagnose CLI and daemon lockfile

Layer / File(s) Summary
Platform and lifecycle lock support
shared/platform.ts, daemon/lifecycle.ts, test/daemon-lifecycle.test.ts, test/diagnose-lockfile.test.ts
Adds lockPath to platform config, implements lock-file read/write/clear helpers, extends lifecycle dependencies and cleanup to include the lock path, and updates lifecycle tests.
Daemon lock-file runtime wiring
daemon/index.ts
Passes the lock path into daemon lifecycle deps, writes the runtime lock file after startup, and clears it on shutdown, exit, and SIGHUP.
Diagnose snapshot flow
cli/commands/diagnose.ts, cli/lib/status-renderer.ts, test/diagnose-lockfile.test.ts
Builds the diagnose snapshot, detects binary mismatches from installed NMH manifests, probes contexts with timeouts, renders JSON/text output, and updates manifest discovery used by mismatch detection.
CLI command surface
cli/index.ts, cli/help.ts, cli/manifest.ts
Registers diagnose in command dispatch, prevents daemon auto-spawn for it, and adds help/manifest entries for the new command.

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

Possibly related PRs

Suggested reviewers: ronaldeddings

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.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 title clearly describes the new interceptor diagnose command and its debugging snapshot purpose.
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.
✨ 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.

@trillium trillium marked this pull request as ready for review June 12, 2026 17:38

@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 `@cli/commands/diagnose.ts`:
- Around line 24-29: The probeWithTimeout function schedules a timeout that is
never cleared, leaving the timer running and causing artificial tail latency;
modify probeWithTimeout to store the timeout id from setTimeout and clear it
(clearTimeout) as soon as either fn() resolves or rejects so the timer doesn't
keep the process alive—update the Promise.race wrapper around fn() and the
timeout to ensure the timeout is cleared when fn wins (and also clear the
timeout if the timeout path runs), adjusting types for the timeout id
(NodeJS.Timeout vs number) if needed; locate and change the probeWithTimeout
function to implement this cleanup.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: b446d9ff-ec71-4480-a557-9034f81b8f46

📥 Commits

Reviewing files that changed from the base of the PR and between 86e7eb6 and 6b9afd9.

📒 Files selected for processing (3)
  • cli/commands/diagnose.ts
  • cli/help.ts
  • cli/index.ts

Comment thread cli/commands/diagnose.ts
trillium added 2 commits June 12, 2026 10:48
Two fixes:

1. probeWithTimeout: clear the timer in finally{} so it doesn't keep
   the process alive after fn() resolves. CodeRabbit catch on PR Hacker-Valley-Media#105.

2. Context-awareness (brain-svjg): without --context, enumerate ALL
   connected browser contexts via the 'contexts' daemon action and
   probe each one in parallel. In a dual-browser setup (Chrome + Brave)
   both contexts appear side-by-side — the silent mismatch that made
   the previous version misleading is now visible.

   With --context <id>: probe only that context (for targeted use).

   Single 'default' context: output format unchanged (flat, no header).
   Multiple or named contexts: indented under 'context <id>:' blocks.

   --json output promotes tab/extension/elements into a 'contexts'
   array so callers can diff contexts programmatically.
Lock file (daemon/lifecycle.ts, shared/platform.ts, daemon/index.ts):
- New LockFileData type: pid, version, execPath, startedAt, socketPath, wsPort, mode
- writeLockFile() / readLockFile() / clearLockFile() helpers
- checkLockFileDuplicate() exits early when a live duplicate daemon is found
- Daemon writes lock on startup, clears on SIGINT/SIGTERM/SIGHUP
- LOCK_PATH exported from shared/platform.ts ($INTERCEPTOR_LOCK_PATH override)

Binary mismatch detection (cli/commands/diagnose.ts):
- Reads lock file to get the execPath of the daemon that owns the socket
- Reads each browser's NMH manifest to get the path Chrome/Brave will spawn
- When they differ, surfaces a prominent warning immediately after the daemon line

This is the exact signal that was missing in the incident where 'interceptor
tabs' timed out despite Chrome being open: the socket daemon was the Homebrew
binary but the NMH manifest pointed to the dev binary, so the extension and
CLI were talking to different processes.

Output when mismatch detected:
  daemon:    running  (pid 12345, /opt/homebrew/bin/interceptor-daemon)
  ⚠ binary mismatch (chrome):
      socket daemon: /opt/homebrew/bin/interceptor-daemon
      NMH manifest:  /Users/trillium/Projects/interceptor/daemon/interceptor-daemon
      Chrome will spawn the manifest binary; CLI talks to the socket binary.
      Fix: run 'interceptor init' or update the NMH manifest to match.

@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: 3

🧹 Nitpick comments (3)
daemon/lifecycle.ts (1)

30-36: 💤 Low value

Consider adding minimal schema validation to readLockFile.

The function parses JSON and type-asserts it as LockFileData without validating that required fields exist. If the lock file is corrupted or contains valid JSON with a different shape, consumers (like cli/commands/diagnose.ts accessing lock.execPath, lock.startedAt) could receive undefined for expected fields.

Given the lock file is written by this same codebase and read shortly after, the risk is low. However, a minimal check could improve robustness.

♻️ Optional: Add minimal field validation
 export function readLockFile(lockPath: string): LockFileData | null {
   try {
-    return JSON.parse(readFileSync(lockPath, "utf-8")) as LockFileData
+    const data = JSON.parse(readFileSync(lockPath, "utf-8"))
+    if (typeof data?.pid !== "number" || typeof data?.execPath !== "string") {
+      return null
+    }
+    return data as LockFileData
   } catch {
     return null
   }
 }
🤖 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 `@daemon/lifecycle.ts` around lines 30 - 36, readLockFile currently JSON-parses
and type-asserts to LockFileData without verifying required fields, which can
yield undefined for consumers (e.g., cli/commands/diagnose.ts reading
lock.execPath or lock.startedAt); modify readLockFile to perform minimal schema
validation after parsing: ensure the resulting object is non-null, has the
expected keys (at least execPath and startedAt) and proper primitive types
(string/number), and return null if validation fails; reference the readLockFile
function and LockFileData type and update callers to handle a null result safely
if not already.
cli/commands/diagnose.ts (2)

73-80: 💤 Low value

Consider runtime validation of manifest structure.

The type assertion as { path?: string } doesn't verify the actual JSON shape at runtime. If the manifest has unexpected structure (e.g., path is not a string), the function silently returns null rather than reporting the issue.

For a diagnostic tool where surfacing configuration problems is valuable, consider validating the parsed JSON:

♻️ Optional: Add runtime validation
 function readNmhManifestPath(manifestFile: string): string | null {
   try {
-    const manifest = JSON.parse(readFileSync(manifestFile, "utf-8")) as { path?: string }
-    return manifest.path ?? null
+    const manifest = JSON.parse(readFileSync(manifestFile, "utf-8"))
+    if (typeof manifest === "object" && manifest !== null && typeof manifest.path === "string") {
+      return manifest.path
+    }
+    return null
   } catch {
     return null
   }
 }
🤖 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 `@cli/commands/diagnose.ts` around lines 73 - 80, The function
readNmhManifestPath currently asserts the JSON shape but doesn't validate it;
update it to perform runtime validation after parsing: parse manifest into an
unknown, assert it's an object, check that manifest.path exists and is of type
string (e.g. typeof manifest.path === "string"), and if not, surface a clear
diagnostic (throw or return an Error/diagnostic message) rather than silently
returning null; update error handling in readNmhManifestPath to differentiate
parse/file errors from invalid manifest shape and include the offending
value/type in the message so callers can report configuration problems.

191-199: 💤 Low value

Consider clarifying which binary is authoritative in the mismatch message.

The fix suggestion "run 'interceptor init' or update the NMH manifest to match" doesn't indicate which binary should be considered correct. Users may not know whether to align the manifest to the running daemon or vice versa.

Consider making the guidance more specific, e.g., "interceptor init will update the manifest to use the CLI's current binary" or explicitly state which path is expected.

🤖 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 `@cli/commands/diagnose.ts` around lines 191 - 199, The binary mismatch
messaging (in the loop over snap.binaryMismatches) should explicitly state which
binary is authoritative and what 'interceptor init' does: update the NMH
manifest to match the CLI/daemon binary. Update the lines.push for the Fix
and/or surrounding text to mention that the socket daemon path (m.runningPath)
is the expected/authoritative binary, show both paths (m.runningPath and
m.manifestPath) as you're already doing, and change the Fix line to say
something like "'interceptor init' will update the NMH manifest to use the CLI's
current/socket binary (m.runningPath) or run the opposite if you intentionally
want the manifest binary to be authoritative." Ensure you edit the messages
constructed in the for loop over snap.binaryMismatches so users know which path
to align.
🤖 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 `@cli/commands/diagnose.ts`:
- Around line 30-33: NMH_PATHS currently hardcodes macOS paths causing
detectBinaryMismatches to silently fail on Linux/Windows; update the code to
detect platform (process.platform) and build NMH paths using os.homedir() for
POSIX and process.env.APPDATA or process.env.USERPROFILE for Windows, providing
correct per-platform paths (macOS: Library/Application Support/..., Linux:
~/.config/.../NativeMessagingHosts, Windows:
%APPDATA%\...\NativeMessagingHosts), then ensure detectBinaryMismatches reads
from these platform-specific NMH_PATHS and gracefully handles missing env vars
by falling back to os.homedir() or throwing a clear error/log via the same
function names (NMH_PATHS, detectBinaryMismatches).

In `@daemon/index.ts`:
- Around line 514-516: Remove the early signal handlers that call
clearLockFile(LOCK_PATH) followed by process.exit(0) and instead invoke
clearLockFile(LOCK_PATH) from inside gracefulShutdown(); locate and update the
gracefulShutdown function to perform lock cleanup (call
clearLockFile(LOCK_PATH)) as part of its cleanup sequence and let
gracefulShutdown control process.exit timing, and ensure SIGINT/SIGTERM (and
optionally SIGHUP) are hooked to call gracefulShutdown() so pending requests,
servers, PID/socket cleanup and the lock file all run in the same shutdown flow.

In `@daemon/lifecycle.ts`:
- Around line 113-120: clearDaemonRuntimeFiles now calls
deps.unlinkSync(deps.lockPath) in addition to socketPath and pidPath, so the
test expecting deps.unlinked toEqual [deps.socketPath, deps.pidPath] will fail;
update the test assertion that checks deps.unlinked to include deps.lockPath
(i.e. expect deps.unlinked toEqual [deps.socketPath, deps.pidPath,
deps.lockPath]) so it matches the behavior of clearDaemonRuntimeFiles (which
references unlinkSync and lockPath).

---

Nitpick comments:
In `@cli/commands/diagnose.ts`:
- Around line 73-80: The function readNmhManifestPath currently asserts the JSON
shape but doesn't validate it; update it to perform runtime validation after
parsing: parse manifest into an unknown, assert it's an object, check that
manifest.path exists and is of type string (e.g. typeof manifest.path ===
"string"), and if not, surface a clear diagnostic (throw or return an
Error/diagnostic message) rather than silently returning null; update error
handling in readNmhManifestPath to differentiate parse/file errors from invalid
manifest shape and include the offending value/type in the message so callers
can report configuration problems.
- Around line 191-199: The binary mismatch messaging (in the loop over
snap.binaryMismatches) should explicitly state which binary is authoritative and
what 'interceptor init' does: update the NMH manifest to match the CLI/daemon
binary. Update the lines.push for the Fix and/or surrounding text to mention
that the socket daemon path (m.runningPath) is the expected/authoritative
binary, show both paths (m.runningPath and m.manifestPath) as you're already
doing, and change the Fix line to say something like "'interceptor init' will
update the NMH manifest to use the CLI's current/socket binary (m.runningPath)
or run the opposite if you intentionally want the manifest binary to be
authoritative." Ensure you edit the messages constructed in the for loop over
snap.binaryMismatches so users know which path to align.

In `@daemon/lifecycle.ts`:
- Around line 30-36: readLockFile currently JSON-parses and type-asserts to
LockFileData without verifying required fields, which can yield undefined for
consumers (e.g., cli/commands/diagnose.ts reading lock.execPath or
lock.startedAt); modify readLockFile to perform minimal schema validation after
parsing: ensure the resulting object is non-null, has the expected keys (at
least execPath and startedAt) and proper primitive types (string/number), and
return null if validation fails; reference the readLockFile function and
LockFileData type and update callers to handle a null result safely if not
already.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1c14bac4-58cf-4b11-aad4-517a6823afc3

📥 Commits

Reviewing files that changed from the base of the PR and between 383d9a7 and e65c300.

📒 Files selected for processing (5)
  • cli/commands/diagnose.ts
  • daemon/index.ts
  • daemon/lifecycle.ts
  • shared/platform.ts
  • test/daemon-lifecycle.test.ts

Comment thread cli/commands/diagnose.ts Outdated
Comment thread daemon/index.ts Outdated
Comment thread daemon/lifecycle.ts
Comment on lines +113 to +120
export function clearDaemonRuntimeFiles(deps: Pick<LifecycleDeps, "unlinkSync" | "pidPath" | "lockPath" | "socketPath" | "isWin" | "log">, reason: string): void {
deps.log(`clearing daemon runtime files: ${reason}`)
if (!deps.isWin) {
try { deps.unlinkSync(deps.socketPath) } catch {}
}
try { deps.unlinkSync(deps.pidPath) } catch {}
try { deps.unlinkSync(deps.lockPath) } catch {}
}

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

Test will fail: clearDaemonRuntimeFiles now unlinks three files but test expects two.

The test at test/daemon-lifecycle.test.ts:72 asserts:

expect(deps.unlinked).toEqual([deps.socketPath, deps.pidPath])

With the addition of unlinkSync(deps.lockPath) at line 119, the actual result will be [socketPath, pidPath, lockPath], causing the test to fail.

🐛 Update the test expectation

In test/daemon-lifecycle.test.ts, update line 72:

-    expect(deps.unlinked).toEqual([deps.socketPath, deps.pidPath])
+    expect(deps.unlinked).toEqual([deps.socketPath, deps.pidPath, deps.lockPath])
🤖 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 `@daemon/lifecycle.ts` around lines 113 - 120, clearDaemonRuntimeFiles now
calls deps.unlinkSync(deps.lockPath) in addition to socketPath and pidPath, so
the test expecting deps.unlinked toEqual [deps.socketPath, deps.pidPath] will
fail; update the test assertion that checks deps.unlinked to include
deps.lockPath (i.e. expect deps.unlinked toEqual [deps.socketPath, deps.pidPath,
deps.lockPath]) so it matches the behavior of clearDaemonRuntimeFiles (which
references unlinkSync and lockPath).

@ronaldeddings

Copy link
Copy Markdown
Collaborator

Thanks for this contribution — the binary-mismatch detection is exactly the kind of failure-mode visibility we want, and we're adopting it. Heads up: I'm pushing a few maintainer commits on top of your branch (maintainer-edits is enabled) to (1) merge current main (v0.22.1 moved the CLI dispatch/help surfaces), (2) fold lock-file cleanup into the daemon's existing gracefulShutdown so the iOS/CDP manager teardown still runs, (3) drop the startup duplicate-check in favor of the WS-port singleton gate that landed in #104 after you opened this (the lock file stays, as metadata for diagnose), and (4) make the context probes aware of ios:/cdp: contexts. Your commits and authorship stay untouched. Full rationale in prd/PRD-121 in the repo.

ronaldeddings and others added 8 commits July 3, 2026 15:47
# Conflicts:
#	cli/help.ts
#	cli/index.ts
#	daemon/index.ts
The standalone SIGINT/SIGTERM/SIGHUP handlers registered before
gracefulShutdown's listeners; their process.exit(0) prevented the later
listeners from ever running (verified on Bun 1.3.11 — first-registered
listener wins), skipping cdpManager/iosManager teardown: open CDP
sockets, WDA sessions, and iOS tunnel/forward/runner child processes.

Lock cleanup now rides the existing shutdown paths (gracefulShutdown +
the exit listener), and SIGHUP joins SIGTERM/SIGINT as a graceful path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop checkLockFileDuplicate and its startup hard-exit: duplicate
prevention is already the WS-port singleton gate (Hacker-Valley-Media#104), which is
atomic where a lock-file PID check is advisory and racy — a recycled
PID after a crash would wedge startup with an error naming a command
('interceptor kill') that doesn't exist, bypassing the existing
stale-PID recovery in decideDaemonStartupRole.

The lock file stays, written only after winning the singleton gate so
a losing duplicate can never clobber the winner's record. Mode label
'relay' → 'native-singleton': relay paths never return from
bootstrapDaemonRole, so a non-standalone process that reaches the
write is serving in-process after spawn-failure fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The daemon's contexts list includes extension ids plus ios:/cdp:
manager contexts; firing tab_list/get_a11y_tree at a phone or CDP app
routes to those managers and renders a misleading 'extension not
responding' for a healthy device. Non-extension contexts now report
kind + connected (presence in the list is the liveness signal) and
skip the browser probes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
diagnose hardcoded its own two-entry Chrome/Brave manifest map while
scripts/install.sh configures eight browser variants on macOS (Beta,
Canary, Dev, Chrome for Testing, Edge, Vivaldi). Both status's
detectConfiguredBrowsers and diagnose's mismatch detector now read one
table in status-renderer, so a dev-build manifest in any installable
browser is caught.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- cli/manifest.ts: diagnose capability entry (usage/summary/returns)
  per the PRD-118 agent-discovery contract, including the status
  (pre-flight) vs diagnose (post-failure) framing.
- test/diagnose-lockfile.test.ts: lock write/read/clear roundtrips,
  corrupt/absent → null, clearDaemonRuntimeFiles unlinks the lock,
  mismatch detection across the install.sh browser set (incl. corrupt
  and pathless manifests), and a spawned 'diagnose --json' run with
  fixture HOME/lock proving daemon-down reporting + the NO_DAEMON
  never-auto-spawn contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live-on-device check deferred (phone not visible to usbmuxd during
verification); the early-return path is what the acceptance criterion
protects and is pinned here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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 `@daemon/index.ts`:
- Around line 1412-1416: The socket-listen failure path in the daemon startup
catch block can leave a stale lock file because `writeLockFile()` happens before
the `process.on("exit")` cleanup is registered. Update the error handling around
the `socket listen` try/catch in `daemon/index.ts` so the `LOCK_PATH` is removed
before calling `process.exit(1)`, or move the exit cleanup registration earlier
in the startup flow to guarantee the lock file is cleared on failure.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 922b8df3-d757-44eb-97d8-0c158f9148af

📥 Commits

Reviewing files that changed from the base of the PR and between e65c300 and d38c248.

📒 Files selected for processing (9)
  • cli/commands/diagnose.ts
  • cli/help.ts
  • cli/index.ts
  • cli/lib/status-renderer.ts
  • cli/manifest.ts
  • daemon/index.ts
  • daemon/lifecycle.ts
  • test/daemon-lifecycle.test.ts
  • test/diagnose-lockfile.test.ts
💤 Files with no reviewable changes (2)
  • test/daemon-lifecycle.test.ts
  • test/diagnose-lockfile.test.ts
✅ Files skipped from review due to trivial changes (1)
  • cli/help.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • cli/index.ts
  • cli/commands/diagnose.ts

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

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 `@daemon/index.ts`:
- Around line 1412-1416: The socket-listen failure path in the daemon startup
catch block can leave a stale lock file because `writeLockFile()` happens before
the `process.on("exit")` cleanup is registered. Update the error handling around
the `socket listen` try/catch in `daemon/index.ts` so the `LOCK_PATH` is removed
before calling `process.exit(1)`, or move the exit cleanup registration earlier
in the startup flow to guarantee the lock file is cleared on failure.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 922b8df3-d757-44eb-97d8-0c158f9148af

📥 Commits

Reviewing files that changed from the base of the PR and between e65c300 and d38c248.

📒 Files selected for processing (9)
  • cli/commands/diagnose.ts
  • cli/help.ts
  • cli/index.ts
  • cli/lib/status-renderer.ts
  • cli/manifest.ts
  • daemon/index.ts
  • daemon/lifecycle.ts
  • test/daemon-lifecycle.test.ts
  • test/diagnose-lockfile.test.ts
💤 Files with no reviewable changes (2)
  • test/daemon-lifecycle.test.ts
  • test/diagnose-lockfile.test.ts
✅ Files skipped from review due to trivial changes (1)
  • cli/help.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • cli/index.ts
  • cli/commands/diagnose.ts
🛑 Comments failed to post (1)
daemon/index.ts (1)

1412-1416: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate relevant symbols and read only the needed slices.
rg -n "writeLockFile|clearLockFile|process\.on\\(\"exit\"|socket listen failed|Bun\.listen|LOCK_PATH" daemon/index.ts

echo '--- lines 560-610 ---'
sed -n '560,610p' daemon/index.ts

echo '--- lines 1390-1435 ---'
sed -n '1390,1435p' daemon/index.ts

echo '--- lines 1630-1675 ---'
sed -n '1630,1675p' daemon/index.ts

Repository: Hacker-Valley-Media/Interceptor

Length of output: 6500


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the file structure to avoid missing surrounding control flow.
wc -l daemon/index.ts
ast-grep outline daemon/index.ts --view expanded

Repository: Hacker-Valley-Media/Interceptor

Length of output: 4523


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('daemon/index.ts')
text = p.read_text()
for needle in ['writeLockFile', 'clearLockFile', 'process.on("exit"', 'socket listen failed', 'Bun.listen']:
    print(f'=== {needle} ===')
    for i, line in enumerate(text.splitlines(), 1):
        if needle in line:
            start = max(1, i-5); end = min(len(text.splitlines()), i+8)
            for j in range(start, end+1):
                print(f'{j}: {text.splitlines()[j-1]}')
            print()
PY

Repository: Hacker-Valley-Media/Interceptor

Length of output: 6573


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant control flow around initialization and shutdown hooks.
nl -ba daemon/index.ts | sed -n '540,620p'
echo '---'
nl -ba daemon/index.ts | sed -n '1388,1432p'
echo '---'
nl -ba daemon/index.ts | sed -n '1638,1668p'

Repository: Hacker-Valley-Media/Interceptor

Length of output: 209


Clear the lock file on socket-listen failure.
writeLockFile() runs before the process.on("exit") cleanup is registered, so process.exit(1) here can leave a stale LOCK_PATH behind. Clear it in this catch, or register the exit handler earlier.

🤖 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 `@daemon/index.ts` around lines 1412 - 1416, The socket-listen failure path in
the daemon startup catch block can leave a stale lock file because
`writeLockFile()` happens before the `process.on("exit")` cleanup is registered.
Update the error handling around the `socket listen` try/catch in
`daemon/index.ts` so the `LOCK_PATH` is removed before calling
`process.exit(1)`, or move the exit cleanup registration earlier in the startup
flow to guarantee the lock file is cleared on failure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ronaldeddings ronaldeddings merged commit f2d8cab into Hacker-Valley-Media:main Jul 3, 2026
1 of 2 checks passed
@ronaldeddings

Copy link
Copy Markdown
Collaborator

Hey @trillium that last message was AI-assisted. Just wanted to give you a big THANK YOU for the great contribution. I love the collab that we have going 💪

@trillium

trillium commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Awesome! This tool is great, its become a regular part of my workflow, happy to contribute back when I run into issues and frustrations :)

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