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:
Codeman maintainer
2026-09-07 22:42:20 +02:00
parent a49be03f96
commit f1b7283393
8 changed files with 65 additions and 24 deletions
+6 -7
View File
@@ -106,6 +106,7 @@ import {
import { DEFAULT_TMUX_HISTORY_LIMIT } from './config/terminal-history.js';
import { EXEC_TIMEOUT_MS } from './config/exec-timeout.js';
import { getCli } from './config/cli-registry/registry.js';
import { compileVersionRegex } from './config/cli-registry/patterns.js';
import { resolveSessionCliVersion } from './utils/cli-resolver.js';
import {
buildInteractiveArgs,
@@ -2469,13 +2470,11 @@ export class Session extends EventEmitter {
private _workingLinePattern(): RegExp {
if (this._workingLineRe === undefined) {
const src = getCli(this.mode)?.capabilities.workDetect?.workingLine;
// The schema validates `workingLine` at load time, so a throw here would mean a
// registry that never loaded. Falling back beats taking the session down.
try {
this._workingLineRe = src ? new RegExp(src) : CLAUDE_WORKING_LINE_PATTERN;
} catch {
this._workingLineRe = CLAUDE_WORKING_LINE_PATTERN;
}
// Same guard the schema applies, not a second opinion: `compileVersionRegex()` is
// what keeps a nested quantifier out of this pattern, and this one runs on the PTY
// hot path. It returns null rather than throwing, and Claude's pattern is the
// fallback every session used before the registry carried one.
this._workingLineRe = (src ? compileVersionRegex(src) : null) ?? CLAUDE_WORKING_LINE_PATTERN;
}
return this._workingLineRe;
}