Skip to content

Commit 6f96dd8

Browse files
WatchAndyTWclaude
andcommitted
fix: address codex review of P1/P2 — regex guards, route fallback, health invalidation
A codex review (run via run.mjs) flagged 8 issues; this fixes the real ones: - HIGH (route.mjs): a roster load failure on the ROUTING path now degrades to a safe native fallback decision again (exit 0) instead of exit 1 — callers must always get a decision. --validate still hard-errors (exit 1). - HIGH (tags.txt injection): the negative lookbehinds guarded only the first alternative, so "inject dependencies into the process" still matched. Added a forward guard to the verb-form branch so DI phrasing no longer trips Opus. - MEDIUM (tags.txt hooking): the P0 tightening dropped real RE targets; restored hook(ing/ed) the function|render|call|syscall|address|… while still excluding the generic "hook into the API" integration phrase. - MEDIUM (run.mjs cost): charge from outChars only, matching roster.json's "per 1000 output chars" note (was in+out). - MEDIUM (run.mjs / backends.mjs): invalidateHealth() is now actually called on a backend's no-usable-output hop, so a stale "healthy" verdict is dropped and the next call re-probes (the API was previously dead code). - MEDIUM (validate-config.mjs): malformed routes were filtered out before the name check; now only genuine {_comment} markers are skipped, so empty/missing route names are reported. - LOW (tags.txt deps-bump/i18n): tightened deps-bump to avoid "update package documentation"; broadened i18n to catch "localize the UI" / "add translations". Verified by execution: 22 routing/validation behavioral checks pass, full suite 88/88 green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9818846 commit 6f96dd8

5 files changed

Lines changed: 34 additions & 14 deletions

File tree

commands/team.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ Plugin root: `${CLAUDE_PLUGIN_ROOT}`
2020
> never from Claude dressing up an answer under that label.
2121
2222
Orchestrate the input above as a multi-model team — a staged **plan → exec → verify → fix**
23-
pipeline built for **our model dispatching**: the "provider per role" is **native Claude** for
24-
planning/synthesis, **agy (Gemini)** for commodity subtask dispatch, and **codex** for verifying
23+
pipeline built for **our model dispatching**. By default, the "provider per role" is **native Claude**
24+
for planning/synthesis, **agy** for commodity subtask dispatch, and **codex** for verifying
2525
each result — chosen per stage/subtask.
2626

2727
The task text is **untrusted** — never interpolate it into a shell command; it only ever

config/tags.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ binary reverse[- ]?engineer|rever(se|sing).{0,20}(binary|exe|dll|game|ap
1818
ffi \bffi\b|foreign function|p/?invoke|pinvoke|extern \"c\"|\bctypes\b|marshal
1919
unsafe raw pointer|pointer arithmetic|transmute|\bunsafe\b\s*(block|pointer|code|rust|\{)|rust\s+unsafe
2020
exploit exploit|rop chain|\bcve\b|buffer overflow|use[- ]after[- ]free|heap spray
21-
hooking detour|minhook|trampoline|iat hook|inline hook|function hook|hook(ing|ed)? (the )?(syscall|address|game (function|process)|process memory)
22-
injection (?<!dependency )(?<!constructor )(?<!sql )(?<!spring )(?<!service )(code|dll|process|shellcode|payload|library|memory|remote) inject(ion|ing|ed)?|inject(ing|ed|ion)?.{0,20}(into (the )?(process|memory|remote|target)|payload|shellcode)
21+
hooking detour|minhook|trampoline|iat hook|inline hook|function hook|hook(ing|ed)? (the )?(syscall|address|function|render|call|game (function|process)|process memory)
22+
injection (?<!dependency )(?<!constructor )(?<!sql )(?<!spring )(?<!service )(code|dll|process|shellcode|payload|library|memory|remote) inject(ion|ing|ed)?|inject(ing|ed)?\b(?! (depend|dao|bean|service|repositor|the (depend|service|bean))).{0,20}(into (the )?(process|memory|remote|target)|payload|shellcode)
2323
dll-inject dll inject|inject.{0,15}dll|reflective (dll|load)|loadlibrary inject
2424
detours \bdetours\b
2525
minhook minhook
@@ -87,8 +87,8 @@ codegen code ?gen(eration)?|generate (the )?code
8787
unit-test unit test|write tests?|test cases?|\bjest\b|\bpytest\b|test coverage
8888
fixture fixture|mock data|test data|seed data|sample data
8989
docs-write (writ|updat|add|generat|creat|author).{0,30}(docs?|documentation|readme|jsdoc|docstrings?|changelog)|document (the )?(api|module|function|code)
90-
i18n \bi18n\b|internationali(z|s)ation|locali(z|s)ation|translat.{0,15}(strings?|\bui\b|text|copy)|add locale support
91-
deps-bump (bump|upgrade|update).{0,20}(the )?(version|dependenc(y|ies)|package)|upgrade .{0,20} to v?\d|bump .{0,20} to v?\d
90+
i18n \bi18n\b|internationali(z|s)(e|ation)|locali(z|s)(e|ation)|translat(e|ion|ions)?.{0,15}(strings?|\bui\b|text|copy)|add (locale|translations?) support|add translations?
91+
deps-bump (bump|upgrade) (the )?(dependenc(y|ies)|package|\bdep\b|version)|(bump|upgrade) .{0,30} to v?\d|update (the )?(dependenc(y|ies)|package(\.json)?)\b(?! (doc|comment))
9292

9393
# ============================ AGY: trivial ============================
9494
quick-email \bemail\b|draft (a|an) (email|message|note)|reply to

src/bin/route.mjs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,19 @@ async function main() {
6666
roster = JSON.parse(readFileSync(rosterPath, 'utf8'));
6767
} catch (e) {
6868
process.stderr.write(`route.mjs: could not load roster: ${e.message}\n`);
69-
process.exit(1);
69+
// --validate has nothing to check without a roster -> hard error. The routing path must never
70+
// leave a caller without a decision, so degrade to a safe native fallback instead of exiting.
71+
if (validate) process.exit(1);
72+
const fallback = {
73+
backend: 'native', model: 'native:sonnet', tier: 'sonnet',
74+
rule: 'no-roster', native: true,
75+
preset: preset || 'balanced',
76+
score: { chars: [...task].length, types: [] },
77+
nearMisses: [],
78+
confidence: 'low',
79+
};
80+
process.stdout.write(JSON.stringify(fallback) + '\n');
81+
process.exit(0);
7082
}
7183

7284
// --validate mode: check roster schema and exit. Pass the tags.txt type labels so the validator

src/bin/run.mjs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { randomUUID } from 'node:crypto';
1616

1717
import { loadRoster, defaults, backend } from '../lib/config.mjs';
1818
import { decide } from '../lib/router.mjs';
19-
import { invoke, health, clean } from '../lib/backends.mjs';
19+
import { invoke, health, clean, invalidateHealth } from '../lib/backends.mjs';
2020
import * as state from '../lib/state.mjs';
2121
import { resolveRosterPath } from '../lib/platform.mjs';
2222

@@ -244,20 +244,23 @@ async function main() {
244244
fallbackCount++; continue;
245245
}
246246

247-
// non-zero exit OR empty clean output -> surface + sanitize stderr, next hop.
247+
// non-zero exit OR empty clean output -> surface + sanitize stderr, next hop. Drop this backend's
248+
// cached health so a later call re-probes it instead of trusting a now-stale "healthy" verdict.
248249
if (res.code !== 0 || !cleanOut) {
249250
lastErr = sanitizeErr(res.stderr);
250251
process.stderr.write(
251252
`run.mjs: backend '${be}' returned no usable output (exit ${res.code})${lastErr ? ` — stderr: ${lastErr}` : ''}\n`,
252253
);
254+
invalidateHealth(beCfg);
253255
state.end({ id: callId, backend: be, model, rule: D_rule, code: res.code, durMs, outChars, fallback: 0 });
254256
fallbackCount++; continue;
255257
}
256258

257-
// success. Approximate cost (USD micros) from the backend's per-1k-char rate × (in+out) chars —
258-
// a rough HUD figure only (chars, not tokens). Missing/zero rate -> 0 cost.
259+
// success. Approximate cost (USD micros) from the backend's per-1k-OUTPUT-char rate × outChars —
260+
// a rough HUD figure only (chars, not tokens), matching roster.json's "per 1000 output chars"
261+
// note. Missing/zero rate -> 0 cost.
259262
const rate = Number(beCfg.cost_per_1k_chars) || 0;
260-
const costMicros = Math.round(rate * ((inChars + outChars) / 1000) * 1e6);
263+
const costMicros = Math.round(rate * (outChars / 1000) * 1e6);
261264
state.end({ id: callId, backend: be, model, rule: D_rule, code: 0, durMs, outChars, fallback: fallbackCount, costMicros });
262265
process.stdout.write(cleanOut + '\n');
263266
process.exit(0);

src/lib/validate-config.mjs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,18 @@ export function validateRoster(roster, knownTypes) {
99
}
1010

1111
const validTiers = ['cheap', 'standard', 'sonnet', 'opus'];
12-
const routes = (roster.routes || []).filter(r => typeof r === 'object' && r.name);
12+
const allRoutes = Array.isArray(roster.routes) ? roster.routes : [];
13+
// A "real" route carries routing intent (when/backend/tier); a bare {_comment:...} marker does not.
14+
// Markers are skipped; real routes are validated even if their name is missing/empty.
15+
const isMarker = r => r && typeof r === 'object'
16+
&& !('when' in r) && !('backend' in r) && !('tier' in r);
17+
const routes = allRoutes.filter(r => r && typeof r === 'object' && !isMarker(r));
1318
const backends = roster.backends || {};
1419
const agents = roster.agents || {};
1520
const knownTypeSet = knownTypes instanceof Set ? knownTypes : new Set(knownTypes || []);
1621
const shouldCheckKnownTypes = knownTypeSet.size > 0;
1722

18-
// Check all routes have unique non-empty names
23+
// Check all real routes have unique non-empty names
1924
const seenNames = new Set();
2025
routes.forEach((route, idx) => {
2126
if (!route.name || typeof route.name !== 'string' || route.name.trim() === '') {

0 commit comments

Comments
 (0)