mirror of
https://github.com/dtzp555-max/ocp.git
synced 2026-07-22 05:25:08 +00:00
fix(tui): pass CLAUDE_CODE_OAUTH_TOKEN to spawned claude + reap defunct sessions
Root cause (PI231 incident): tmux does not forward the parent's env to the pane, so the TUI claude never saw CLAUDE_CODE_OAUTH_TOKEN and fell back to ~/.claude/.credentials.json, whose single-use refresh token got corrupted to an empty string by the per-request spawn + kill-session teardown racing claude's token rotation -> permanent "Please run /login" 401 (re-login re-corrupted on the next spawn). Connected leak: the pane's claude is a child of the tmux server (not node), so kill-session left <defunct> zombies the server never reaped (25 over 30 days; tmux kill-server dropped it 25->3). Fix 1: buildTuiCmd now adds CLAUDE_CODE_OAUTH_TOKEN=<shq-escaped> to the pane env prefix when the env is set, so claude authenticates via the long-lived token and never touches the credentials.json refresh path (matching stable hosts). Unset -> no token added (credentials.json-only hosts unaffected). Fix 2: reapStaleTuiSessions kill-servers after clearing our own sessions ONLY when no foreign tmux session remains (never disrupts a co-hosted olp-tui-*). kill-server is the only node-reachable action that ACTUALLY reaps -- server exit reparents survivors to init, which waitpids them; a per-session kill cannot, since node is not the zombies' parent. Added a 15-min periodic reap (server.mjs) gated on TUI_MODE and on the TUI path being idle. Residual: a request whose pane is created in the idle-check/kill-server window fails cleanly via the existing honesty gates (documented). ALIGNMENT: Class B (OCP-owned TUI spawn). cli.js does NOT perform either operation -- there is no cli.js analogue for "how the TUI pane authenticates" or "reaping tmux-server-owned zombies"; authorized by ADR 0007 (PR-C amendment) per ALIGNMENT.md's Class B citation requirement. No Class A wire surface, no endpoint shape, no alignment.yml token, and no models.json entry touched. Tests: +6 in test-features.mjs (buildTuiCmd token set/unset/shq-injection; reaper kill-server ours-only / foreign-present / no-server). 241 passed, 0 failed. Co-Authored-By: Claude <claude-opus> <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude <claude-opus> <noreply@anthropic.com>
parent
c86e3d014f
commit
6394ca3265
+23
@@ -495,6 +495,28 @@ const cacheCleanupInterval = setInterval(() => {
|
||||
}
|
||||
}, 600000);
|
||||
|
||||
// TUI defunct-session reap (periodic): the boot reap (below) only fires once, but a
|
||||
// long-lived host (PI231 ran 30 days without restart) accumulates defunct `<claude>`
|
||||
// zombies between restarts — the pane's claude is a child of the tmux server, not node,
|
||||
// so only the server can reap it (see reapStaleTuiSessions). We sweep every 15 min, but
|
||||
// ONLY when the TUI path is fully idle: reapStaleTuiSessions may `kill-server`, which would
|
||||
// tear down a live turn's pane, so we skip the sweep while any turn is inflight or queued.
|
||||
// RESIDUAL (documented, accepted): a brand-new request whose pane is created in the narrow
|
||||
// window between this idle-check and kill-server would have its pane torn down and fail the
|
||||
// turn cleanly via runTuiTurn's existing honesty gates (rare; the boot reap is the primary
|
||||
// mechanism and the 15-min cadence makes the window negligible).
|
||||
// Gated on TUI_MODE — zero effect (no kill-server, no list-sessions) when TUI is off.
|
||||
// cli.js does NOT perform this operation (Class B, OCP-owned TUI spawn) — see ADR 0007.
|
||||
const TUI_REAP_INTERVAL_MS = 15 * 60 * 1000;
|
||||
const tuiReapInterval = TUI_MODE ? setInterval(() => {
|
||||
if (tuiSemaphore.inflight > 0 || tuiSemaphore.queued > 0) return; // a turn is live — defer
|
||||
try {
|
||||
const n = reapStaleTuiSessions();
|
||||
if (n) logEvent("info", "tui_reaped_stale_sessions", { count: n, trigger: "periodic" });
|
||||
} catch (e) { logEvent("error", "tui_periodic_reap_failed", { error: e.message }); }
|
||||
}, TUI_REAP_INTERVAL_MS) : null;
|
||||
if (tuiReapInterval && typeof tuiReapInterval.unref === "function") tuiReapInterval.unref();
|
||||
|
||||
// ── Active child process tracking ────────────────────────────────────────
|
||||
const activeProcesses = new Set();
|
||||
|
||||
@@ -2284,6 +2306,7 @@ function gracefulShutdown(signal) {
|
||||
clearInterval(sessionCleanupInterval);
|
||||
clearInterval(authCheckInterval);
|
||||
clearInterval(cacheCleanupInterval);
|
||||
if (tuiReapInterval) clearInterval(tuiReapInterval);
|
||||
closeDb();
|
||||
|
||||
// 3. Kill all active child processes
|
||||
|
||||
Reference in New Issue
Block a user