Skip to content

fix(cli): --json emits raw, byte-exact JSON — no Rich value mutation (#69) - #70

Merged
nycomp merged 1 commit into
mainfrom
fix/69-json-raw-output
Oct 23, 2026
Merged

nycomp merged 1 commit into
mainfrom
fix/69-json-raw-output

Conversation

@nycomp

@nycomp nycomp commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #69.

print_json routed every --json output through Rich's display-oriented JSON renderable, which soft-wraps long string values at the console width by inserting literal \n characters into the values. Piped/consumed output was corrupted — and a value that wrapped mid-string came out as invalid JSON (raw newline inside a string literal). This bit the post-Railway-incident secret rotation: a vault-stored DB URI round-tripped with an embedded newline mid-hostname.

The fix

  • print_json writes plain json.dumps(data, indent=2) straight to stdout on the machine path — byte-exact values, always spec-valid, no Rich renderable, no terminal-width dependence.
  • The highlighted rendering is kept as display-only, resolved automatically:
    • interactive TTY → Rich rendering (unchanged UX for humans; its wrapping is cosmetic);
    • pipes/scripts/CI (non-TTY) → raw JSON;
    • CAMPUS_CLI_AGENT=1 → raw even on a TTY, for agent tools that allocate a pty (issue proposal 3);
    • explicit pretty=True/False still overrides.
  • The old pretty=False branch (console.print(json.dumps(...))) had the same wrap hazard (plus Rich markup interpretation of […] in output) — the raw path now bypasses Rich entirely.

auth status --json already did the right thing (typer.echo(json.dumps(...))); this makes every other --json command match that contract. All ~30 print_json call sites (vault get/list, client list/get/rotate, grants, user, audit keys/traces) are covered without changes.

Tests

  • New tests/unit/test_output.py: byte-exact long-value round-trip, spec-valid raw JSON, pretty routing on a forced TTY, CAMPUS_CLI_AGENT forcing raw on a TTY (and ignoring falsy values).
  • TestAuditJsonShapes gains the console-width round-trip regression (audit keys list --json with a 110-char value) — verified to fail against the old code.
  • Full suite: 310 passed; ruff check clean.
  • Manually verified the three routing behaviors against real stdout and a pty.

Docs

README gains a short "Machine-readable output" section documenting the guarantee and the CAMPUS_CLI_AGENT=1 opt-out.

…69)

Rich's JSON renderable soft-wrapped long string values at the console
width, inserting literal newlines into the data: every machine consumer
of --json got corrupted values, and a value that wrapped mid-string came
out as invalid JSON. Bit the post-Railway-incident secret rotation — a
vault-stored DB URI came back with a newline mid-hostname.

print_json now writes plain json.dumps to stdout on the machine path
(never the display renderable). The highlighted rendering is reserved
for interactive TTYs — display-only wrapping there — and
CAMPUS_CLI_AGENT=1 forces the raw form for agent tools that allocate a
pty. The old pretty=False branch had the same wrap/markup hazard; the
raw path now bypasses Rich entirely.
@nycomp
nycomp merged commit a05efc1 into main Oct 23, 2026
4 checks passed
@nycomp
nycomp deleted the fix/69-json-raw-output branch October 23, 2026 04:24
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.

--json output corrupts long string values (Rich display wrapping); add raw machine-readable output mode

1 participant