Skip to content

v0.42.15.0 fix: decouple CLI primary output from process.stdout.isTTY (#1784)#1806

Merged
garrytan merged 7 commits into
masterfrom
garrytan/fix-istty-stdout-gating
Jun 3, 2026
Merged

v0.42.15.0 fix: decouple CLI primary output from process.stdout.isTTY (#1784)#1806
garrytan merged 7 commits into
masterfrom
garrytan/fix-istty-stdout-gating

Conversation

@garrytan

@garrytan garrytan commented Jun 3, 2026

Copy link
Copy Markdown
Owner

Closes #1784.

Several gbrain subcommands gated their primary output (or the amount of work they did) on process.stdout.isTTY. Run them from a subagent / pipe / cron and they silently switched branches — JSON when you wanted human text, or nothing useful. The read paths (get/list/search/query) were already clean; this fixes the commands that weren't, and establishes the repo rule across them: human by default, JSON only with --json; a terminal controls cosmetics (live dashboard, color, loop cadence), never what data prints.

What's in this PR (5 commits)

feat(eval): cycle-default — new src/core/eval/cycle-default.ts: one source of truth for the TTY=3 / non-TTY=1 cycle default + the banner-suffix helper. Library-core stays TTY-agnostic; the CLI layer owns the TTY upgrade + annotation.

fix(jobs): decouple 'jobs watch' — splits two tangled axes: --json (FORMAT) and --follow (LOOP, default isTTY && !json). Non-TTY now prints one human snapshot and exits instead of an endless JSON stream; --follow opts back into the continuous stream. TTY live dashboard unchanged. Pure resolveWatchMode(opts, isTTY) extracted so the full matrix is unit-tested. JSDoc rewritten (the false "stdin close quits" claim corrected).

fix(reindex): human cost-refusalreindex-code still refuses to re-embed non-interactively without --yes (exit 2, no spend), but the refusal is now human text unless --json is passed. Extracted into pure buildCostRefusal().

fix(eval): annotate non-interactive defaultseval cross-modal + eval takes-quality (run + regress) annotate their existing banner when the cycle count is the silent non-TTY default (cycles: 1 (non-interactive default; --cycles N for more)). eval suspected-contradictions does the same for its $1 non-TTY budget (added budgetUsdExplicit so the annotation is accurate). takes-quality-eval/runner.ts core defaults to the constant (no process.stdout.isTTY read in library code).

chore: bump version and changelog — VERSION + package.json → 0.42.15.0, CHANGELOG, TODOS.

Out of scope (filed as TODOs)

  • sync.ts:2491 has the byte-identical cost-refusal-format bug — documented gate, deserves its own change.
  • agent/book-mirror follow: isTTY are the correct loop-gate (identical data, overridable), not bugs.
  • jobs --help has no subcommand list (pre-existing gap).

Tests

  • New: test/eval/cycle-default.test.ts, test/reindex-cost-refusal.test.ts, test/eval-suspected-contradictions-budget-default.test.ts, test/jobs-watch-mode.test.ts (the format×loop matrix), test/e2e/non-tty-output.serial.test.ts (the cmd </dev/null contract the issue asked for).
  • 32 new/related unit + e2e pass; existing jobs-watch + takes-quality tests pass; bun run verify 29/29 green.

Review

  • /plan-eng-review CLEAR; codex outside-voice on the plan (3 material findings adopted) and an adversarial pass on the final diff (1 finding — TTY+--json looping forever — fixed via resolveWatchMode + the matrix test before this PR).

🤖 Generated with Claude Code

Documentation

  • CLAUDE.md — Key Files notes added/updated for the v0.42.15.0 wave: jobs.ts (the jobs watch format/loop split + resolveWatchMode + the new cycle-default.ts module), reindex-code.ts (human cost-refusal + buildCostRefusal), eval-cross-modal.ts (cycle/budget banner annotation + resolveCycleDefault/cycleDefaultSuffix).
  • CHANGELOG.md## [0.42.15.0] entry (ELI10 lead + "To take advantage" block).
  • TODOS.md — filed the two deferred follow-ups (sync.ts:2491 human refusal; jobs --help subcommand table).
  • llms-full.txt — regenerated from CLAUDE.md (build:llms test green).
  • Coverage: all shipped surface (the jobs watch flags, the reindex refusal format, the eval annotations, cycle-default.ts) has reference coverage in CLAUDE.md. No README change needed (CLI-flag-level behavior). No architecture-diagram drift.

garrytan and others added 7 commits June 2, 2026 22:53
Decouple CLI primary output from process.stdout.isTTY (#1784): human by
default, JSON only with --json; jobs watch non-TTY one-shots; eval banners
annotate non-interactive defaults; reindex-code refusal is human unless --json.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#1784)

Annotate jobs.ts (jobs watch format/loop split + resolveWatchMode + the new
cycle-default.ts), reindex-code.ts (human cost-refusal), and eval-cross-modal.ts
(cycle/budget banner annotations). Regenerate llms-full.txt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…stdout-gating

# Conflicts:
#	CHANGELOG.md
#	CLAUDE.md
#	TODOS.md
#	VERSION
#	llms-full.txt
#	package.json
@garrytan garrytan merged commit 488f89e into master Jun 3, 2026
21 checks passed
mgunnin added a commit to mgunnin/gbrain that referenced this pull request Jun 3, 2026
* upstream/master:
  v0.42.23.0 feat(jobs): --nice scheduling-priority flag for jobs work/supervisor (garrytan#1815) (garrytan#1820)
  v0.42.22.0 fix(minions): supervisor progress watchdog + worker DB self-defense — alive-but-wedged worker self-heals (garrytan#1801) (garrytan#1824)
  v0.42.21.0 fix(postgres): module-singleton ownership — canonical landing for the dream-cycle "connect() has not been called" class (garrytan#1404/garrytan#1471/garrytan#1619) (garrytan#1805)
  v0.42.20.0 fix: reliability wave — PGLite capture lock-pin + Postgres reconnect race + search embed-hang (garrytan#1762 garrytan#1745 garrytan#1775) (garrytan#1810)
  v0.42.19.0 fix(skillopt): close the last gap in the AI SDK v6 tool-loop fix (write-capture mapper + regression test) (garrytan#1809)
  v0.42.18.0 fix: sync orphan-pileup watchdog (garrytan#1633) + links-lag µs stamp (garrytan#1768) (garrytan#1807)
  v0.42.17.0 fix(sync): resumable incremental sync — killed mid-import no longer loses progress (garrytan#1794) (garrytan#1808)
  v0.42.16.0 feat(doctor): brain health as a solved problem — cause-ranked doctor + OOM-loop line + auto-drain + pool-reap (garrytan#1685) (garrytan#1802)
  v0.42.15.0 fix: decouple CLI primary output from process.stdout.isTTY (garrytan#1784) (garrytan#1806)
  v0.42.14.0 fix(zero-config): code-* readiness signal + init embedding-key validation + lock self-heal (garrytan#1780) (garrytan#1804)
  v0.42.13.0 fix(search): archive/ content findable by default, demoted not hard-excluded (garrytan#1777) (garrytan#1797)
  v0.42.12.0 feat: self-upgrading gbrain — invocation-riding update check + opt-in auto-upgrade (garrytan#1798)
  v0.42.11.0 feat(skillopt): held-out eval gate, honest receipts, ENFORCE + ablation opts (garrytan#1759)
  v0.42.10.0 feat(extract): opt-in global-basename wikilink resolution (closes garrytan#972) (garrytan#1388)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI: stdout suppressed / altered under non-TTY (isTTY-gated output eats stdout in subagent/exec/cron/pipe)

1 participant