fix(security-classifier): TDZ when claude CLI is missing from PATH

The checkTranscript Promise executor in browse/src/security-classifier.ts
referenced `finish()` at the !claude early-return guard before declaring
it 5 lines later. JavaScript throws ReferenceError: Cannot access 'finish'
before initialization (TDZ) for that path, but the path is only reachable
when resolveClaudeCommand returns null inside the spawn block (a TOCTOU
window vs. the outer checkHaikuAvailable cache).

Fix: hoist `let stdout = ''`, `let done = false`, and `const finish` block
above `const claude = resolveClaudeCommand()` so finish is in scope before
any reference to it. Behavior is identical when claude is on PATH; the
fix only matters for the dormant missing-CLI degraded path.

Adds browse/test/security-classifier-tdz.test.ts as the regression guard:
clears PATH + override env vars, calls checkTranscript, asserts the result
serializes with degraded:true and a meaningful reason field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-05-11 23:20:07 -07:00
parent bed1a9f5ed
commit 4b3bbed242
2 changed files with 79 additions and 8 deletions
+11 -8
View File
@@ -500,6 +500,17 @@ export async function checkTranscript(params: {
// timeout rate in the v1.5.2.0 ensemble bench because of this, plus
// ~44k cache_creation tokens per call (massive cost inflation).
// Using os.tmpdir() gives Haiku a clean context for pure classification.
// TDZ fix: declare `finish` BEFORE `resolveClaudeCommand` so the early
// return at the !claude guard below doesn't ReferenceError. Triggered
// only when claude CLI is missing from PATH (dormant otherwise).
let stdout = '';
let done = false;
const finish = (signal: LayerSignal) => {
if (done) return;
done = true;
resolve(signal);
};
const claude = resolveClaudeCommand();
if (!claude) {
return finish({ layer: 'transcript_classifier', confidence: 0, meta: { degraded: true, reason: 'claude_cli_not_found' } });
@@ -511,14 +522,6 @@ export async function checkTranscript(params: {
'--output-format', 'json',
], { stdio: ['ignore', 'pipe', 'pipe'], cwd: os.tmpdir() });
let stdout = '';
let done = false;
const finish = (signal: LayerSignal) => {
if (done) return;
done = true;
resolve(signal);
};
p.stdout.on('data', (d: Buffer) => (stdout += d.toString()));
p.on('exit', (code) => {
if (code !== 0) {