fix(agent-claude-code): tighten waiting_input regex to ignore static UI footer

The existing `/bypass.*permissions/i` regex matched Claude's persistent UI footer `⏵⏵ bypass permissions on (shift+tab to cycle)` — which is visible on EVERY active Claude session. As long as native JSONL classification returned a definitive answer, this didn't matter. The previous noise-skip commit on this branch (faf43042) made many more sessions fall through to the AO JSONL pipeline, which exposed the bug: ao-143/144/151 all flipped to waiting_input even though they were genuinely dormant.

Tightened the regex to require "all future" — matching the actual permission-bypass prompt ("bypass all future permissions for this session") rather than the footer toggle ("bypass permissions on"). The other two waiting_input regexes (`Do you want to proceed?`, `(Y)es...(N)o`) are unchanged.

Verified: dashboard's stored AO activity-JSONL entries had `state: "waiting_input"` with trigger snippets containing the persistent footer text. With this fix, classifyTerminalOutput on the exact same terminal capture returns active instead.

Tests: existing `bypass all future permissions for this session` test still passes (the prompt phrase contains the new tighter pattern). Added a regression test confirming the static footer alone does NOT match waiting_input.
This commit is contained in:
harshitsinghbhandari 2026-05-19 20:48:42 +05:30
parent faf43042bd
commit 8aeffdcd86
2 changed files with 24 additions and 1 deletions

View File

@ -310,7 +310,13 @@ export function classifyTerminalOutput(terminalOutput: string): ActivityState {
const tail = lines.slice(-5).join("\n");
if (/Do you want to proceed\?/i.test(tail)) return "waiting_input";
if (/\(Y\)es.*\(N\)o/i.test(tail)) return "waiting_input";
if (/bypass.*permissions/i.test(tail)) return "waiting_input";
// Match the ACTUAL permission-bypass prompt — "bypass all future permissions
// for this session" — not the persistent UI footer "bypass permissions on
// (shift+tab to cycle)" which is visible on EVERY Claude session. The
// earlier `/bypass.*permissions/i` was too broad and false-fired on every
// session that fell through to the AO JSONL pipeline (real-world repro:
// ao-143/144/151 all flipped to waiting_input on dormant sessions).
if (/bypass\s+all\s+future\s+permissions/i.test(tail)) return "waiting_input";
return "active";
}

View File

@ -538,6 +538,23 @@ describe("detectActivity", () => {
);
});
it("does NOT match Claude's persistent UI footer 'bypass permissions on (shift+tab to cycle)'", () => {
// Regression test: the old `/bypass.*permissions/i` regex matched this
// footer toggle (visible on EVERY Claude session) and falsely fired
// waiting_input for every session that fell through to the AO JSONL
// pipeline. ao-143/144/151 all flipped to waiting_input on dormant
// sessions until this was tightened to require "all future".
const footerOnly = [
"✻ Crunched for 11s",
"",
"──────────────────────────────────────────────────────────",
" ",
"──────────────────────────────────────────────────────────",
" ⏵⏵ bypass permissions on (shift+tab to cycle)",
].join("\n");
expect(agent.detectActivity(footerOnly)).not.toBe("waiting_input");
});
it("returns active when queued message indicator is visible", () => {
expect(agent.detectActivity("Press up to edit queued messages\n")).toBe("active");
});