fix(916): the Codex runner runs beside the lenses, only the fallback waits; head_sha described everywhere; the docs keep their thresholds

The runner builds nothing, so serialising it only added its wait to the
critical path; the worktree-isolated fallback is what must follow the lenses,
and the harness case now records lens count at the FALLBACK's start alone.
setTimeout in the harness is globalThis.setTimeout (the .mjs lint config has
ES builtins only). head_sha carries the same description in both scripts and
every fixer/implementer prompt asks for the worktree HEAD, not a PR head. The
mechanics page says why the cap stays at one after the serialisation and
restores the 20%/10% RAM thresholds by key; the record says "several", not
"three".

Decisions-Edit: yes
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV
This commit is contained in:
2026-09-05 12:43:58 +02:00
co-authored by Claude Fable 5.1
parent f7a0da4051
commit 929dff835a
6 changed files with 52 additions and 41 deletions
@@ -42,26 +42,24 @@ function run(script, args, { reviewRounds, landOverrides = {}, postRebase = null
const calls = [];
let round = 0;
let fixCount = 0;
let lensesInFlight = 0;
const xfamilyStartedWithLensesInFlight = [];
let lensesReturned = 0;
const lensesReturnedWhenFallbackStarted = [];
const agent = async (prompt, opts) => {
const label = (opts && opts.label) || '';
calls.push({ label, prompt, opts });
if (label.startsWith('impl:') || label.startsWith('fix:#')) return { done: true, summary: '', verified: '', left: '', commits: IMPL_COMMITS, head_sha: 'aaaaaaa' };
if (label.startsWith('review:codex:')) {
xfamilyStartedWithLensesInFlight.push(lensesInFlight);
return { verdict: 'merge', findings: [], ran: false };
}
if (label.startsWith('review:fallback:')) {
xfamilyStartedWithLensesInFlight.push(lensesInFlight);
lensesReturnedWhenFallbackStarted.push(lensesReturned);
const spec = reviewRounds[round - 1] || { findings: [] };
return { verdict: 'merge', findings: spec.findings };
}
if (label.startsWith('review:')) {
const spec = reviewRounds[round - 1] || { findings: [] };
lensesInFlight += 1;
await new Promise((resolve) => setTimeout(resolve, 5));
lensesInFlight -= 1;
await new Promise((resolve) => globalThis.setTimeout(resolve, 5));
lensesReturned += 1;
if (spec.lensesNull) return null;
return { verdict: 'merge', findings: spec.findings };
}
@@ -81,7 +79,7 @@ function run(script, args, { reviewRounds, landOverrides = {}, postRebase = null
}
return Promise.all(thunks.map((t) => t().catch(() => null)));
};
return script(args, agent, parallel, () => {}, () => {}).then((result) => ({ result, calls, fixCount, xfamilyStartedWithLensesInFlight }));
return script(args, agent, parallel, () => {}, () => {}).then((result) => ({ result, calls, fixCount, lensesReturnedWhenFallbackStarted }));
}
const cases = [
@@ -173,13 +171,13 @@ describe.each(cases)('%s review loop', (name, args) => {
expect(result.error).toMatch(/^the post-rebase review round produced no reviews/);
});
it('on a rubric-class change the cross-family step starts only after both lenses have returned', async () => {
it('on a rubric-class change the worktree-isolated fallback reviewer starts only after both lenses have returned', async () => {
const rubricArgs = { ...args, risk: 'rubric' };
const { result, calls, xfamilyStartedWithLensesInFlight } = await run(script, rubricArgs, { reviewRounds: [{ findings: [] }] });
const { result, calls, lensesReturnedWhenFallbackStarted } = await run(script, rubricArgs, { reviewRounds: [{ findings: [] }] });
expect(result.error).toBeUndefined();
expect(calls.filter((c) => c.label.startsWith('review:codex:')).length).toBe(1);
expect(calls.filter((c) => c.label.startsWith('review:fallback:')).length).toBe(1);
expect(xfamilyStartedWithLensesInFlight).toEqual([0, 0]);
expect(lensesReturnedWhenFallbackStarted).toEqual([2]);
expect(finisherPrompt(calls)).toContain('substituted a cold same-family review-only agent');
});