feat(statusline): wrap the user's own real statusline instead of skipping it

Now that the exporter no longer lives in a fixed per-case file, it can
compose with the user's actual configured statusline rather than just
backing off when one is found.

findEffectiveUserStatusLineCommand() walks Claude Code's own settings
precedence for a workspace: project-local .claude/settings.local.json
> project-shared .claude/settings.json > the user's global
~/.claude/settings.json. A legacy Codeman-marked entry left behind in
the project's own settings.local.json is never treated as a real user
command — it's skipped and precedence continues to the next layer.

The shared exporter script (bumped to a V2 marker so stale copies
self-heal) now fires the telemetry POST in a background subshell —
its own stdout/stderr discarded so nothing leaks into the visible
statusline, and confirmed non-blocking (~4ms, even against an
unreachable endpoint) — then, if the pane's environment carries
CODEMAN_USER_STATUSLINE_CMD, feeds it the same stdin blob and relays
its stdout as ours. Otherwise it falls back to the plain "codeman"
marker as before.

The discovered command is threaded to the pane via `tmux setenv
CODEMAN_USER_STATUSLINE_CMD` (_configureStatusLineUserCommand) rather
than embedded in the spawn command line, for the same
premature-shell-expansion reason as the parent commit: tmux stores a
setenv value verbatim and never re-parses it, so once shellescape()d
for that one command, the command's own $/quotes survive untouched
into the pane's environment.

Verified live via direct shell execution of the generated script
(both branches: fallback and user-command wrapping) before deploy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015GyMnFWnUzc41TDeHg9juW
This commit is contained in:
timkjr
2026-09-07 19:18:36 -05:00
co-authored by Claude Sonnet 5
parent d4aa3c8cca
commit e15e8e43e8
3 changed files with 160 additions and 8 deletions
+32 -1
View File
@@ -67,7 +67,7 @@ import {
legacyConfigForMode,
} from './session-cli-registry-bridge.js';
import type { CliEntry } from './config/cli-registry/types.js';
import { resolveStatusLineCliCommand } from './hooks-config.js';
import { resolveStatusLineCliCommand, findEffectiveUserStatusLineCommand } from './hooks-config.js';
import {
buildSshConnectionArgs,
defaultRemoteCommandForMode,
@@ -1707,6 +1707,30 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
}
}
/**
* Export the user's own REAL statusLine command (found by
* findEffectiveUserStatusLineCommand) via tmux setenv, so the shared
* exporter script (statusLineExporterScriptContent in hooks-config.ts) can
* wrap it. Via setenv rather than embedding it in the spawn command line:
* tmux stores a setenv value verbatim and never re-parses it as shell
* syntax, so once safely escaped for THIS one command, the command's own
* `$`/quotes survive untouched into the claude process's environment — the
* same reasoning that made the exporter script itself necessary (see
* ensureStatusLineExporterScript's doc comment). Only this ONE line needs
* shellescape(); the stored value itself is opaque to tmux from then on.
*/
private _configureStatusLineUserCommand(muxName: string, command: string | undefined): void {
if (!command) return;
try {
execSync(`${this.tmux()} setenv -t ${shellescape(muxName)} CODEMAN_USER_STATUSLINE_CMD ${shellescape(command)}`, {
timeout: EXEC_TIMEOUT_MS,
stdio: 'ignore',
});
} catch {
// Non-critical — the exporter just falls back to the plain "codeman" marker.
}
}
/**
* Creates a new tmux session wrapping Claude CLI or a shell.
* In test mode: creates an in-memory session only (no real tmux session).
@@ -1799,6 +1823,10 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
mode === 'claude' && !remote && !docker
? await resolveStatusLineCliCommand(workingDir, statusLineTelemetry === true)
: undefined;
// The user's own REAL statusLine, if any (walked via Claude Code's own
// settings precedence) — exported below so the shared exporter script
// can wrap it. Only worth discovering when we're actually injecting.
const userStatusLineCommand = statusLineCommand ? await findEffectiveUserStatusLineCommand(workingDir) : undefined;
const baseCmd = buildSpawnCommand({
mode,
@@ -1879,6 +1907,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
mode,
legacyConfigForMode(mode, options as unknown as Record<string, unknown>)
);
this._configureStatusLineUserCommand(muxName, userStatusLineCommand);
// Apply user-supplied env overrides (e.g., CLAUDE_CODE_EFFORT_LEVEL) via tmux setenv
// so secret values stay off the bash command line. Must run before respawn-pane.
@@ -2062,6 +2091,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
mode === 'claude' && !remote && !docker
? await resolveStatusLineCliCommand(workingDir, statusLineTelemetry === true)
: undefined;
const userStatusLineCommand = statusLineCommand ? await findEffectiveUserStatusLineCommand(workingDir) : undefined;
const baseCmd = buildSpawnCommand({
mode,
@@ -2099,6 +2129,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
mode,
legacyConfigForMode(mode, options as unknown as Record<string, unknown>)
);
this._configureStatusLineUserCommand(muxName, userStatusLineCommand);
// Re-apply user env overrides before respawn so the new shell inherits them.
this.applyEnvOverrides(muxName, envOverrides);