Skip to content

Clean up JSON output: logging helpers, stderr routing, and lifecycle signals#249

Merged
umair-ably merged 9 commits intomainfrom
jsonFixes
Apr 1, 2026
Merged

Clean up JSON output: logging helpers, stderr routing, and lifecycle signals#249
umair-ably merged 9 commits intomainfrom
jsonFixes

Conversation

@umair-ably
Copy link
Copy Markdown
Collaborator

@umair-ably umair-ably commented Mar 31, 2026

Targets #248

Intent

This PR makes JSON output reliable and predictable for agent/script consumers, while cleaning up inconsistencies that accumulated across the codebase. The specific problems addressed:

  1. JSON mode omitted useful status information. Connection state changes, progress messages, and warnings were silently swallowed in --json mode. Agents had no way to observe connection lifecycle or know why a command was stalling.

  2. Mixed and inconsistent output routing. Some commands used this.log(), others this.logToStderr(), others console.log() for status messages. JSON guards (shouldOutputJson) were applied inconsistently — some commands guarded progress messages, others didn't, leading to human-readable text leaking into JSON stdout.

  3. No command completion signal. JSON consumers had no way to know when a command finished — they had to rely on process exit, which doesn't distinguish clean exit from crash. Agents need a definitive "done" signal.

  4. Status text polluted stdout. Success messages, progress indicators, and warnings were written to stdout alongside data, making it impossible for JSON consumers to get clean machine-parseable output without filtering.

  5. --force flag was silently required in JSON mode. Commands with confirmation prompts would hang in JSON mode (no TTY to prompt). The flag description didn't communicate this.

Commits

Commit Type Files Description
Add logging helpers and JSON lifecycle events to base command Behavioral 3 New logProgress, logSuccessMessage, logListening, logHolding, logWarning helpers on AblyBaseCommand. JSON completed signal in finally(). Connection/channel state events in JSON mode. Updates chat-base-command and stats-base-command.
Add NDJSON test helpers for multi-record JSON output Infrastructure 1 parseJsonOutput() and parseAllJsonRecords() in test/helpers/ndjson.ts for tests that now receive multiple JSON records on stdout.
Update --force flag description to indicate JSON mode requirement Behavioral 1 Flag description → "(required with --json)" in src/flags.ts.
Migrate commands to use logging helpers and stderr for status output Mechanical (skim) ~99 Same pattern in every command: replace this.log(formatProgress/Success/Listening/Warning(...)) with this.logProgress/logSuccessMessage/logListening/logWarning(msg, flags), remove shouldOutputJson guards around status messages.
Update unit tests for stderr status messages and NDJSON output Mechanical (skim) ~104 Tests switch stdoutstderr assertions for status messages, use parseJsonOutput for multi-record JSON.
Update E2E tests for stderr status messages Mechanical (skim) 9 Same stdout→stderr pattern in E2E tests.
Update documentation, skills, and AGENTS.md Docs 7 AGENTS.md, README, and skill files updated to document new helpers and patterns.

Test plan

  • pnpm prepare succeeds
  • pnpm exec eslint . — 0 errors
  • pnpm test:unit — all pass
  • pnpm test:e2e — verify stderr routing in real subprocess execution

🤖 Generated with Claude Code

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 31, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cli-web-cli Ready Ready Preview, Comment Apr 1, 2026 0:07am

Request Review

@claude-code-ably-assistant
Copy link
Copy Markdown

Walkthrough

This PR standardises JSON output across the entire CLI by introducing five logging helpers on AblyBaseCommand that route status messages to stderr (human) or structured JSON (agents/scripts), while keeping stdout clean for data-only output. It also adds a {"type":"status","status":"completed"} signal emitted at the end of every command so agent/script consumers have a definitive "done" indicator. The change is mechanical across ~99 command files and ~113 test files, with all the behavioral logic concentrated in src/base-command.ts.

Changes

Area Files Summary
Base / Infrastructure src/base-command.ts, src/chat-base-command.ts Add logProgress, logSuccessMessage, logListening, logHolding, logWarning helpers; emit JSON completed signal in finally(); route connection/channel state events to JSON in --json mode
Commands — Accounts / Apps / Auth accounts/login, logout, switch; apps/create, delete, switch, update, rules/*; auth/keys/*, revoke-token Mechanical: replace this.log(formatX(...)) + manual shouldOutputJson guards with this.logProgress/logSuccessMessage/logWarning(msg, flags)
Commands — Channels / Bench channels/* (publish, subscribe, presence, occupancy, annotations, batch-publish, history, etc.); bench/* Same mechanical migration; stdout now carries data only, stderr carries status
Commands — Rooms / Chat rooms/messages/*, rooms/presence/*, rooms/reactions/*, rooms/typing/*, rooms/occupancy Same migration; rooms/presence/enter and rooms/presence/subscribe have slightly larger diffs due to hold-state restructuring
Commands — Spaces spaces/cursors/*, spaces/locations/*, spaces/locks/*, spaces/create, spaces/get, spaces/list Same migration
Commands — Push / Queues / Integrations / Logs / Connections push/**, queues/*, integrations/*, logs/*, connections/test Same migration; connections/test has a larger diff (+55/-64) due to per-item error restructuring
Tests — Unit ~104 files under test/unit/ Switch stdout assertions to stderr for status messages; use parseJsonOutput() for tests that now receive NDJSON (result + completed signal)
Tests — E2E 9 files under test/e2e/ Same stdout→stderr switch for status/progress/success assertions
Test Helpers test/helpers/ndjson.ts New parseJsonOutput() and parseAllJsonRecords() helpers for multi-record NDJSON output
Config src/flags.ts --force description updated to note it is required in JSON mode
Docs / Skills AGENTS.md, README.md, .claude/skills/** Document new helpers and patterns; regenerate README from metadata

Review Notes

  • Breaking change for --json consumers. Status messages that previously appeared on stdout (mixed with data) are now on stderr or emitted as structured JSON status records. Scripts that parsed stdout as a single JSON object will need to use the new NDJSON format (one JSON record per line) and filter by type field. The PR body documents this clearly.
  • New completed signal — every command now emits {"type":"status","status":"completed","exitCode":0|1} as its last stdout line in JSON mode. Consumers must handle this extra record.
  • logListening / logHolding emit to stdout in JSON mode (not stderr), so agents see them inline with data records. logProgress / logSuccessMessage are silent in JSON mode. Reviewers should verify this asymmetry is intentional and consistently applied across subscribe vs. hold commands.
  • rooms/presence/enter and rooms/presence/subscribe have larger-than-mechanical diffs — worth a closer read to confirm no behavioral regression in the enter/hold flow.
  • E2E tests not fully run yet — the PR checklist marks pnpm test:e2e as unchecked. Reviewers should confirm these pass before merging.
  • No new runtime dependencies introduced.

@sacOO7
Copy link
Copy Markdown
Contributor

sacOO7 commented Apr 1, 2026

Found few inconsistencies

  • --token-only overrides --json, causing raw text to appear in JSON output
  • handleInvalidKey writes to stdout and shows an interactive prompt without JSON protection, which can hang execution
  • auth keys revoke has an unguarded secondary confirmation prompt in JSON mode, leading to hangs
  • push publish and push batch-publish skip confirmation in JSON mode without requiring --force, creating inconsistent safety behavior

@sacOO7
Copy link
Copy Markdown
Contributor

sacOO7 commented Apr 1, 2026

  • formatLimitWarning uses this.log() instead of this.logToStderr(), making output handling inconsistent with similar functions
  • stats-base-command.ts is not updated — debug and status messages go to stdout and use this.warn() instead of logWarning()
  • Base command warnings don’t use the logWarning() helper, so they aren’t visible to JSON-based tools
  • --force flag is defined inline instead of using a shared forceFlag, leading to inconsistent flag descriptions

Introduce five logging helpers on AblyBaseCommand that route status
messages to stderr (human) or structured JSON (agents/scripts):
- logProgress/logSuccessMessage: silent in JSON mode
- logListening/logHolding/logWarning: emit structured JSON status events

Also adds:
- JSON completed signal in finally() so agents know when a command ends
- Connection and channel state change events emitted in JSON mode
- Cleanup timeout/error messages moved to stderr unconditionally
- chat-base-command and stats-base-command updated to use new helpers
Add parseJsonOutput() and parseAllJsonRecords() to test/helpers/ndjson.ts.
These handle stdout that now contains multiple JSON records (e.g. result +
completed signal) in both compact NDJSON and pretty-printed formats.
Flag description now reads "(required with --json)" since JSON mode
cannot show interactive confirmation prompts.
Mechanical migration across all ~99 command files:
- Replace this.log(formatProgress/Success/Listening/Warning(...)) with
  this.logProgress/logSuccessMessage/logListening/logWarning(msg, flags)
- Remove manual shouldOutputJson guards around status messages
- Status text now routes to stderr (human) or structured JSON (agents)
- stdout reserved for data output only
Mechanical test updates across ~104 test files:
- Switch stdout assertions to stderr for status/progress/success messages
- Use parseJsonOutput() for JSON tests that now receive multiple records
  (result + completed signal)
- Adjust JSON output parsing to handle NDJSON format
E2E tests updated to check stderr instead of stdout for status/progress
messages, matching the new output routing where status goes to stderr
and only data goes to stdout.
- AGENTS.md: document logging helpers, completed signal, JSON hold status
- Skills: update patterns, review checks, and code examples to reflect
  new logProgress/logSuccessMessage/logListening/logHolding/logWarning
  helpers instead of old manual formatX + shouldOutputJson pattern
- README.md: regenerated from command metadata
…rr, enforce --force

- Respect --json with --token-only in auth token commands
- Auto-remove invalid/revoked keys in JSON mode instead of hanging on prompts
- Enforce --force flag for push publish commands in JSON mode
- Route formatLimitWarning output to stderr (19 commands)
- Migrate this.warn() to this.logWarning() for structured JSON warnings
- Fix premature logListening emission before subscription is ready
- Fix shouldOutputJson({}) dead code in bench commands
Copy link
Copy Markdown
Contributor

@sacOO7 sacOO7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good to me

- Check stderr (not stdout) for status messages like "Entered presence",
  "Location set in space:", and "Waiting for"
- Parse NDJSON correctly: find type "result" line for history, first line
  for list (completed status signal is appended as last line)
@umair-ably umair-ably merged commit d61dc90 into main Apr 1, 2026
14 of 15 checks passed
@umair-ably umair-ably deleted the jsonFixes branch April 1, 2026 12:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants