fix(statusline): unset a stale user statusline var, write the exporter script atomically

Three small follow-ups from the #361 review.

A tmux setenv survives respawn-pane, so _configureStatusLineUserCommand
returning early when the user has no statusline left a previously
exported CODEMAN_USER_STATUSLINE_CMD in place: a user who deleted their
own statusline kept getting the stale one wrapped, and lost Codeman's
footer print-through, until the tmux session was recreated. It now
issues `setenv -u` in that case, the same shape as the effort-level
cleanup in applyEnvOverrides.

ensureStatusLineExporterScript truncated and rewrote a script that live
sessions execute on every statusline render, and chmod'd it after the
write. It now writes a temp file next to the target, chmods that, and
rename()s it into place.

The non-tmux direct-PTY fallback carries no exporter; that is now stated
at the spawn site and in the architecture-invariants paragraph rather
than left as a silent gap.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-09-14 15:59:00 +02:00
parent 707ea345eb
commit fa1ea8d9fe
5 changed files with 66 additions and 6 deletions
+26
View File
@@ -99,6 +99,32 @@ describe('TmuxManager (unit)', () => {
});
});
describe('statusline user-command env', () => {
// A tmux setenv survives respawn-pane, so the absence of a user statusline
// must UNSET the variable rather than leave a stale one for the exporter
// to wrap.
it('unsets CODEMAN_USER_STATUSLINE_CMD when the user has no statusline', () => {
mockedExecSync.mockClear();
(
manager as unknown as { _configureStatusLineUserCommand: (m: string, c?: string) => void }
)._configureStatusLineUserCommand('codeman-abc', undefined);
const cmds = mockedExecSync.mock.calls.map((c) => String(c[0]));
expect(cmds.some((c) => c.includes("setenv -t 'codeman-abc' -u CODEMAN_USER_STATUSLINE_CMD"))).toBe(true);
});
it('sets CODEMAN_USER_STATUSLINE_CMD, shell-escaped, when the user has one', () => {
mockedExecSync.mockClear();
(
manager as unknown as { _configureStatusLineUserCommand: (m: string, c?: string) => void }
)._configureStatusLineUserCommand('codeman-abc', `printf '%s' "$1" | jq -r .model`);
const cmds = mockedExecSync.mock.calls.map((c) => String(c[0]));
const setCmd = cmds.find((c) => c.includes('CODEMAN_USER_STATUSLINE_CMD'));
expect(setCmd).toBeDefined();
expect(setCmd).not.toContain(' -u ');
expect(setCmd).toContain("setenv -t 'codeman-abc' CODEMAN_USER_STATUSLINE_CMD ");
});
});
describe('Codex command builder', () => {
it('controls decorative TUI animation through Codex config', () => {
expect(buildCodexCommand({ animations: false })).toBe('codex --config tui.animations=false');