fix(statusline): sticky telemetry collection, footer print-through, EOF fix

Responds to Ark0N's review round on the ephemeral-CLI-flag statusline
injection rework:

- Rebase-detail fixes: registry-gated telemetry eligibility via
  getCli(mode)?.capabilities.statusLineTelemetry instead of a hardcoded
  mode === 'claude' check, using the capability flag master's CLI-registry
  refactor already declares for exactly this purpose.

- Design question settled: sticky (a). Rather than persisting the toggle
  as a new field and threading it through every session-creation path
  (cron, Ralph Loop API, quick-start), eliminated the per-session field
  entirely. readPlanUsageTelemetryEnabled() (hooks-config.ts) reads the
  existing showPlanUsageLimits setting fresh from settings.json at every
  claude create/respawn (TmuxManager.createSession/respawnPane) - no
  per-session state to survive a restart, and it applies uniformly to
  every creation path for free, since they all flow through the same
  TmuxManager methods.

  This required fixing a real bug found along the way: showPlanUsageLimits
  was not actually round-tripping through settings.json on save -
  settings-ui.js explicitly excluded it from the PUT body as a pure
  per-device display key. It now flows through normally (both true and
  false); the load-side per-device merge behavior is unchanged.

  Removed entirely as a result: the statusLineTelemetry field from
  CreateSessionSchema/SettingsUpdateSchema, CreateSessionOptions/
  RespawnPaneOptions, Session._statusLineTelemetry (this is what makes
  the restart-persistence bug moot rather than patched), and the
  frontend send sites.

- Footer print-through restored: the no-user-statusline branch of the
  exporter script now runs the telemetry POST in the foreground so its
  own stdout becomes the in-terminal footer, falling back to a plain
  "codeman" marker only on curl failure.

- Background-subshell EOF fix: the wrap-a-real-statusline branch closes
  stdin too, not just stdout/stderr (`>/dev/null 2>&1 </dev/null &`) -
  the un-redirected subshell process itself, not curl, was what held a
  reader-to-EOF's pipe open for however long curl took to finish. Added
  curl --max-time 5 so a hung (not just refused) Codeman cannot wedge
  the render.

Tests: real-shell-execution tests for the footer/EOF fixes (fake curl
stand-in on PATH, real sh subprocess spawns, real elapsed-time
measurements - verified non-vacuous against a hand-reconstructed
old-style script), unit tests for readPlanUsageTelemetryEnabled.
Adapted two existing tests whose payloads referenced the removed field.
Fixed during independent code review: a stray indentation break and a
test exercising the wrong (legacy) exporter code path.

Docs synced: CLAUDE.md, docs/usage-limits-display-plan.md (old
disk-based section marked superseded, kept for history),
docs/architecture-invariants.md.

Full suite green: 352 files, 6780 passed, 12 skipped, 0 failed.
tsc/lint/format:check/frontend-syntax all clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
timkjr
2026-09-07 20:37:29 -05:00
co-authored by Claude Sonnet 5
parent e15e8e43e8
commit d5b75af628
17 changed files with 288 additions and 143 deletions
+13 -11
View File
@@ -67,7 +67,11 @@ import {
legacyConfigForMode,
} from './session-cli-registry-bridge.js';
import type { CliEntry } from './config/cli-registry/types.js';
import { resolveStatusLineCliCommand, findEffectiveUserStatusLineCommand } from './hooks-config.js';
import {
resolveStatusLineCliCommand,
readPlanUsageTelemetryEnabled,
findEffectiveUserStatusLineCommand,
} from './hooks-config.js';
import {
buildSshConnectionArgs,
defaultRemoteCommandForMode,
@@ -1756,7 +1760,6 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
resumeSessionId,
envOverrides,
effort,
statusLineTelemetry,
historyLimit = DEFAULT_TMUX_HISTORY_LIMIT,
remote,
docker,
@@ -1815,13 +1818,13 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
const envExportsStr = this.buildEnvExports(sessionId, muxName, mode).join(' && ');
// Claude-only, local spawns only (remote/docker have their own separate
// command builders — out of scope here). Also self-heals: strips any
// legacy disk-written exporter from an older Codeman build the first
// time a session starts in that workspace again.
// Registry-gated (capabilities.statusLineTelemetry — claude only today), local
// spawns only (remote/docker have their own separate command builders — out of
// scope here). Also self-heals: strips any legacy disk-written exporter from an
// older Codeman build the first time a session starts in that workspace again.
const statusLineCommand =
mode === 'claude' && !remote && !docker
? await resolveStatusLineCliCommand(workingDir, statusLineTelemetry === true)
getCli(mode)?.capabilities.statusLineTelemetry && !remote && !docker
? await resolveStatusLineCliCommand(workingDir, await readPlanUsageTelemetryEnabled())
: undefined;
// The user's own REAL statusLine, if any (walked via Claude Code's own
// settings precedence) — exported below so the shared exporter script
@@ -2070,7 +2073,6 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
resumeSessionId,
envOverrides,
effort,
statusLineTelemetry,
remote,
docker,
name,
@@ -2088,8 +2090,8 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
// See createSession()'s identical resolution for rationale.
const statusLineCommand =
mode === 'claude' && !remote && !docker
? await resolveStatusLineCliCommand(workingDir, statusLineTelemetry === true)
getCli(mode)?.capabilities.statusLineTelemetry && !remote && !docker
? await resolveStatusLineCliCommand(workingDir, await readPlanUsageTelemetryEnabled())
: undefined;
const userStatusLineCommand = statusLineCommand ? await findEffectiveUserStatusLineCommand(workingDir) : undefined;