Skip to content

Fix #1877: delete a shellper row whose pid was reused, without signaling it - #1878

Merged
waleedkadous merged 9 commits into
mainfrom
builder/bugfix-1877
Nov 6, 2026
Merged

waleedkadous merged 9 commits into
mainfrom
builder/bugfix-1877

Conversation

@waleedkadous

Copy link
Copy Markdown
Contributor

Summary

After a reboot, a persisted terminal_sessions row whose shellper_pid now belongs to an unrelated process was kept forever and retried on every reconcile and /api/state poll. On this machine that was two log lines every ~2s, and tower.log grew to 354 MB. Now that row is deleted the first time Tower sees it, with one INFO line, and the pid is never signaled.

Fixes #1877

Root Cause

SessionManager.reconnectSession() already spots the reuse (PID 2217 reused (start time mismatch)), but it returns only null. Both Bugfix #1686 guards in tower-terminals.ts then checked only processExists(shellper_pid):

  • reconcile Phase 2
  • the on-the-fly reconnect in getTerminalsForWorkspace()

A reused pid is alive, so the row was kept (unconfirmed++ / WARN) and retried, forever.

Reproduced live, read-only. Row fea2d3f9… has pid = shellper_pid = 2217 with a recorded start of 2026-10-13. ps -p 2217 shows /usr/libexec/diagnosticextensionsd, started 2026-10-24 after that day's boot. The socket file is gone.

Fix

Departure from the issue's suggested shape. The sweep re-checks the start time with the shared helper instead of passing a pid-reused reason out of reconnectSession(). Two reasons:

The behaviour the issue prescribes is implemented as written.

Out of scope, flagged for follow-up (same hazard, not part of this issue):

Test Plan

Once this ships, the stale row on this machine clears itself after the next Tower restart. No hand edit of global.db is needed.

🤖 Generated with Claude Code

waleedkadous and others added 4 commits November 4, 2026 08:26
…ing it

After a reboot a persisted terminal_sessions row's shellper_pid can be
reused by an unrelated process. reconnectSession() detects the start-time
mismatch, but both #1686 guards (reconcile Phase 2 and the on-the-fly
reconnect in getTerminalsForWorkspace) checked only processExists(), so
the row was kept and retried on every pass, forever.

Add isShellperPidReused(): true only for a KNOWN start-time mismatch (an
unknown start time on either side is not proof). Inside each guard's
shellperAlive branch, a reused pid now deletes the row and continues
before the stale-row SIGTERM branch (shellper rows persist pid ==
shellper_pid), so the other process is never signaled. Every other
reconnect failure keeps the #1686 WARN/keep behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
waleedkadous and others added 2 commits November 4, 2026 08:41
@waleedkadous

Copy link
Copy Markdown
Contributor Author

Thanks, this is a tidy fix and the write-up made it easy to review. Integration review ran 3-way (gemini, codex, claude): all three APPROVE, none blocking.

Two small things I'd like before merge, both one-liners:

  1. shellper-husk-sweep.ts still defines its own PID_REUSE_TOLERANCE_MS = 2000. Import the constant you just exported from session-manager.ts so the rule has one home.
  2. In the isShellperPidReused doc comment, add a sentence that the comparison is between two clocks (Date.now() at shellper start vs ps -o lstart, second-truncated on macOS), so the 2 s tolerance is understood as a skew budget, not a magic number. A false "reused" verdict now deletes a row that was previously only kept-and-retried; bounded (nothing is signaled, and the row was already unreconnectable), but worth stating.

Already covered elsewhere, no action here: the Phase 1 pre-filter still SIGTERMs on processExists alone, which is your follow-up #1879. Good call splitting it.

Waleed has the gate; I'll relay his decision.

waleedkadous and others added 3 commits November 5, 2026 20:52
…-clock skew budget

- shellper-husk-sweep.ts imports PID_REUSE_TOLERANCE_MS from
  session-manager.ts instead of declaring its own copy, so the reuse rule
  has one home.
- isShellperPidReused's doc comment states that it compares two clocks
  (the shellper's Date.now() vs ps -o lstart / /proc ctime), so the
  tolerance is a skew budget, and that a false verdict only deletes an
  already-unreconnectable row, with nothing signaled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@waleedkadous
waleedkadous merged commit 91bf0d1 into main Nov 6, 2026
7 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.

Tower keeps a dead shellper row forever when its pid is reused after reboot (start-time mismatch ignored by the sweep)

1 participant