Fix new-window context leak showing previous session ctx%
Stop tmux status from falling back to global status files, seed new tmux instances at 0%, rank live sessions by opened_at instead of signals.json keepalive mtime, and bind Grok pids to tmux more reliably.
This commit is contained in:
@@ -62,6 +62,95 @@ describe("dashboard", () => {
|
||||
fs.rmSync(tmp, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("new window: old live session with fresher signals mtime does not steal primary", async () => {
|
||||
// Repro: old tab still live and signals.json keeps updating; new tab has
|
||||
// later opened_at but no/low context — must pick new (ctx ~0), not old 78%.
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "hud-newwin-"));
|
||||
const cwd = "/Users/dex/AI FILM SPACE/0717";
|
||||
const oldId = "old-busy-78";
|
||||
const newId = "new-window-zero";
|
||||
const enc = encodeURIComponent(cwd);
|
||||
const oldDir = path.join(tmp, "sessions", enc, oldId);
|
||||
const newDir = path.join(tmp, "sessions", enc, newId);
|
||||
fs.mkdirSync(oldDir, { recursive: true });
|
||||
fs.mkdirSync(newDir, { recursive: true });
|
||||
|
||||
const oldSignals = {
|
||||
contextWindowUsage: 78,
|
||||
contextTokensUsed: 392180,
|
||||
contextWindowTokens: 500000,
|
||||
turnCount: 18,
|
||||
toolCallCount: 344,
|
||||
primaryModelId: "grok-4.5",
|
||||
};
|
||||
fs.writeFileSync(path.join(oldDir, "signals.json"), JSON.stringify(oldSignals));
|
||||
fs.writeFileSync(
|
||||
path.join(oldDir, "summary.json"),
|
||||
JSON.stringify({
|
||||
info: { id: oldId, cwd },
|
||||
last_active_at: "2026-07-17T06:00:00.000Z",
|
||||
updated_at: "2026-07-17T06:00:00.000Z",
|
||||
created_at: "2026-07-17T04:00:00.000Z",
|
||||
current_model_id: "grok-4.5",
|
||||
}),
|
||||
);
|
||||
// Touch signals again so mtime is "now" (old session keepalive)
|
||||
const now = Date.now();
|
||||
fs.utimesSync(path.join(oldDir, "signals.json"), new Date(now), new Date(now));
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(newDir, "summary.json"),
|
||||
JSON.stringify({
|
||||
info: { id: newId, cwd },
|
||||
last_active_at: "2026-07-17T08:00:00.000Z",
|
||||
updated_at: "2026-07-17T08:00:00.000Z",
|
||||
created_at: "2026-07-17T08:00:00.000Z",
|
||||
current_model_id: "grok-4.5",
|
||||
}),
|
||||
);
|
||||
// Explicit empty/low context for new window
|
||||
fs.writeFileSync(
|
||||
path.join(newDir, "signals.json"),
|
||||
JSON.stringify({
|
||||
contextWindowUsage: 0,
|
||||
contextTokensUsed: 0,
|
||||
contextWindowTokens: 500000,
|
||||
turnCount: 0,
|
||||
toolCallCount: 0,
|
||||
primaryModelId: "grok-4.5",
|
||||
}),
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(tmp, "active_sessions.json"),
|
||||
JSON.stringify([
|
||||
{
|
||||
session_id: oldId,
|
||||
pid: process.pid,
|
||||
cwd,
|
||||
opened_at: "2026-07-17T04:00:00.000Z",
|
||||
},
|
||||
{
|
||||
session_id: newId,
|
||||
pid: process.pid,
|
||||
cwd,
|
||||
opened_at: "2026-07-17T08:00:00.000Z",
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
const r = await refreshDashboard({ grokHome: tmp, noUsage: true });
|
||||
assert.ok(r.session);
|
||||
assert.equal(r.session!.sessionId, newId);
|
||||
assert.ok(
|
||||
Math.round(r.session!.contextPercent) < 5,
|
||||
`new window must be ~0% ctx, got ${r.session!.contextPercent}`,
|
||||
);
|
||||
assert.doesNotMatch(r.title, /78%/);
|
||||
|
||||
fs.rmSync(tmp, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("after /new: prefers newest active session, not first-listed old 78%", async () => {
|
||||
// Repro: active_sessions keeps old tab first; new tab second with low/no signals.
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "hud-new-"));
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import {
|
||||
uniqueTmuxSessionName,
|
||||
sanitizeId,
|
||||
isInProcessTree,
|
||||
parentPid,
|
||||
} from "../src/multi-session.js";
|
||||
import { tmuxStatusCommands } from "../src/tmux-hud.js";
|
||||
|
||||
describe("multi-session isolation", () => {
|
||||
it("uniqueTmuxSessionName never returns fixed grok-hud", () => {
|
||||
@@ -48,4 +51,23 @@ describe("multi-session isolation", () => {
|
||||
assert.equal(isInProcessTree(process.pid, pp), true);
|
||||
}
|
||||
});
|
||||
|
||||
it("tmux status format never falls back to global status (new window isolation)", () => {
|
||||
const home = path.join(os.tmpdir(), "hud-fake-home");
|
||||
const cmds = tmuxStatusCommands(home);
|
||||
const formats = cmds
|
||||
.filter((a) => a[0] === "set" && String(a[2] ?? "").startsWith("status-format"))
|
||||
.map((a) => a.slice(3).join(" "));
|
||||
assert.ok(formats.length >= 1);
|
||||
for (const f of formats) {
|
||||
// Must only read per-session path; global fallback leaked old ctx%
|
||||
assert.match(f, /hud\/tmux\/#\{session_name\}/);
|
||||
assert.doesNotMatch(
|
||||
f,
|
||||
/hud\/tmux-lines\.txt|hud\/tmux-status\.txt(?!\/)/,
|
||||
);
|
||||
// No "|| cat …/hud/tmux-status" style global fallback
|
||||
assert.doesNotMatch(f, /\|\|\s*cat\s+\S*\/hud\/tmux-/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user