From 1dd167ebe086963767e5a9c8d15fe5503d9843d5 Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Sun, 18 Oct 2026 16:48:38 +0200 Subject: [PATCH] fix(story-mode): a bare repository with flags first is no new project repository, and an error is no empty answer `git init -q --bare ../origin.git` (flags before --bare) counted as Claude starting the project's repository. And a last step that printed a usage message or an error (`error: unknown option`, `grep: x: No such file`) read as "the last check finds nothing"; it now fails, as it did. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/story-mode.ts | 10 ++++++++-- test/story-mode.test.ts | 9 ++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/story-mode.ts b/src/story-mode.ts index f43f26c2..228a6629 100644 --- a/src/story-mode.ts +++ b/src/story-mode.ts @@ -2307,7 +2307,13 @@ function checkName(e: StoryEvent | undefined): string { const EMPTY_ANSWER_STEP = /(?:^|[;&|]\s*)(?:e?grep|fgrep|rg|git config|test|\[|which|command -v)\s[^;&|]*$/; const emptyAnswer = (e: StoryEvent): boolean => { const out = e.output ?? ''; - return e.ok === false && /^Exit code 1\n\s*\S/.test(out) && EMPTY_ANSWER_STEP.test(e.label.trim()); + // A usage message or an error is a step that went wrong, not an empty answer. + return ( + e.ok === false && + /^Exit code 1\n\s*\S/.test(out) && + !/^\s*(usage|error|fatal|grep):/im.test(out) && + EMPTY_ANSWER_STEP.test(e.label.trim()) + ); }; /** One or two files by name ("calc.py and test_calc.py"), more by count ("3 files"). */ @@ -2717,7 +2723,7 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde const earlier = events.slice(0, firstAt); const startsRepo = !earlier.some((e) => (e.commits ?? 0) > 0) && - [...earlier, ...evs].some((e) => e.tool === 'Bash' && /\bgit init\b(?!\s+--bare)/.test(e.label)); + [...earlier, ...evs].some((e) => e.tool === 'Bash' && /\bgit init\b(?![^;&|\n]*\s--bare\b)/.test(e.label)); const lead = [ startsRepo ? 'starts a git repository' : '', wrote.length ? `writes ${fileWords(wrote)}` : '', diff --git a/test/story-mode.test.ts b/test/story-mode.test.ts index 2226cde7..70884e06 100644 --- a/test/story-mode.test.ts +++ b/test/story-mode.test.ts @@ -1003,6 +1003,13 @@ describe('buildStoryPage', () => { expect(run('./deploy.sh; which deploy', 'Exit code 2\nno target')).toBe('Claude tries a command, and it fails.'); expect(run('node a.js; grep -q x a.txt', 'Exit code 1')).toBe('Claude tries a command, and it fails.'); expect(run('git config user.name; ./deploy.sh', 'Exit code 1\nme')).toBe('Claude tries a command, and it fails.'); + // A usage message or an error from that last step is not an empty answer. + expect( + run('node a.js; git config --nope', "Exit code 1\nok\nerror: unknown option `nope'\nusage: git config") + ).toBe('Claude tries a command, and it fails.'); + expect(run('node a.js; grep x missing.txt', 'Exit code 1\nok\ngrep: missing.txt: No such file or directory')).toBe( + 'Claude tries a command, and it fails.' + ); }); it('a problem in the last words is an open one: not history, not a negation, not a name', () => { @@ -1082,7 +1089,7 @@ describe('buildStoryPage', () => { expect( save([ prompt('start'), - ...bash('b1', 'git init --bare ../origin.git', 'Remote', ''), + ...bash('b1', 'git init -q --bare ../origin.git', 'Remote', ''), ...bash('b2', 'git commit -qm "add a"', 'Commit', ''), end(1000), ])