Skip to content

fix: suppress UI spinners and telemetry notices when --json is used#742

Open
CosticaPuntaru wants to merge 1 commit intoFission-AI:mainfrom
CosticaPuntaru:fix/clean-json-output
Open

fix: suppress UI spinners and telemetry notices when --json is used#742
CosticaPuntaru wants to merge 1 commit intoFission-AI:mainfrom
CosticaPuntaru:fix/clean-json-output

Conversation

@CosticaPuntaru
Copy link
Contributor

@CosticaPuntaru CosticaPuntaru commented Feb 22, 2026

Description

This PR addresses the issue where OpenSpec CLI output is polluted with spinner text and telemetry notices even when the --json flag is active. This pollution causes parsing errors for automation tools and AI agents.

Changes

  • Updated status, instructions, templates, and new-change commands to skip starting the ora spinner if the json option is present.
  • Modified the global preAction hook in src/cli/index.ts to detect the --json flag and silence the telemetry notice.
  • Added --json support to the new change command to provide machine-readable success/failure details.
  • Added a new capability spec in openspec/specs/machine-readable-output/spec.md to document this behavior.

Testing

  • Verified openspec status --json produces clean JSON.
  • Verified openspec instructions apply --json produces clean JSON.
  • Verified telemetry notice is suppressed on first run with --json.
  • Verified openspec new change test --json produces clean JSON.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added --json flag to CLI commands for clean, machine-readable JSON output
    • UI elements (spinners) are suppressed when --json is used
    • Telemetry notices no longer appear in JSON mode
    • Errors are directed to stderr to prevent stdout pollution, ensuring valid JSON in standard output

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

This pull request implements clean JSON output for the OpenSpec CLI. When --json is specified, the changes suppress spinner UI elements, silence telemetry notices via a shared silence flag passed through the CLI preAction, and ensure errors are routed to stderr. Comprehensive design and specification documentation outlines the requirements and implementation patterns.

Changes

Cohort / File(s) Summary
Documentation & Specifications
openspec/changes/archive/2026-02-22-clean-json-output/.openspec.yaml, openspec/changes/archive/2026-02-22-clean-json-output/design.md, openspec/changes/archive/2026-02-22-clean-json-output/proposal.md, openspec/changes/archive/2026-02-22-clean-json-output/specs/machine-readable-output/spec.md, openspec/changes/archive/2026-02-22-clean-json-output/tasks.md, openspec/specs/machine-readable-output/spec.md
Adds formal design, proposal, specification, and implementation task documents detailing requirements for JSON output cleanliness, spinner suppression, telemetry silencing, and stderr error routing. Includes acceptance criteria and implementation patterns across multiple commands.
CLI & Telemetry Integration
src/cli/index.ts, src/telemetry/index.ts
CLI preAction detects --json flag and passes { silent: true } to telemetry. Added --json option to new-change command. Updated maybeShowTelemetryNotice signature to accept optional { silent?: boolean } parameter for conditional notice suppression.
Workflow Command Spinners
src/commands/workflow/status.ts, src/commands/workflow/instructions.ts, src/commands/workflow/templates.ts, src/commands/workflow/new-change.ts
Defers spinner initialization across all commands: spinners are created without starting and only started when options.json is falsy. New-change command additionally outputs structured JSON on success (containing success, name, schema, path) when JSON mode is active; error paths suppress spinner failures in JSON mode before rethrowing.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 With --json the spinners pause their dance,
No telemetry notices distract or prance,
Stderr catches errors, stdout flows so clean—
Pure JSON whispers, the clearest I've seen! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: suppressing UI spinners and telemetry notices when --json is used, which is the primary objective of this PR.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- Modified status, instructions, templates, and new-change commands to conditionally start the ora spinner only when not in JSON mode.
- Updated the global preAction hook to detect the --json flag and pass a silent flag to telemetry notices.
- Added --json support to 'new change' command for machine-readable result reporting.
- Added a new spec for machine-readable-output capability.

This ensures stdout/stderr pollution does not break JSON parsing for automated tools and AI agents.
@greptile-apps
Copy link

greptile-apps bot commented Feb 22, 2026

Greptile Summary

This PR successfully addresses the issue of UI pollution when --json flag is used by suppressing spinners and telemetry notices across multiple commands. The implementation is clean and consistent.

  • Suppressed ora spinners in status, instructions, templates, and new-change commands when --json is present
  • Silenced telemetry notice in preAction hook when --json flag is detected
  • Added JSON output support to new change command
  • Documented the behavior in a new capability spec

One issue found: The error handler in src/cli/index.ts:509-510 for the new change command still uses ora().fail() which will pollute stdout when --json is used. This violates the spec requirement that errors should go to stderr.

Confidence Score: 4/5

  • Safe to merge after fixing the error handler issue
  • The implementation is solid and addresses the stated requirements. One logical error exists where the error handler doesn't respect --json flag, which could pollute stdout with spinner text during error scenarios. This is a straightforward fix.
  • src/cli/index.ts requires fixing the error handler to respect --json flag

Important Files Changed

Filename Overview
src/cli/index.ts Added JSON flag detection in preAction hook to suppress telemetry notice. Error handler for new change doesn't respect --json flag.
src/commands/workflow/new-change.ts Added conditional spinner start and JSON output format. Error path properly skips spinner failure message when --json is used.
src/telemetry/index.ts Added silent option to maybeShowTelemetryNotice to suppress the telemetry notice when --json is used.

Last reviewed commit: 8c8ab0a

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

7 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link

greptile-apps bot commented Feb 22, 2026

Additional Comments (1)

src/cli/index.ts
Error handler pollutes stdout when --json is used. Should check options.json and write to stderr or output JSON error format.

      if (options.json) {
        console.error(`Error: ${(error as Error).message}`);
      } else {
        console.log();
        ora().fail(`Error: ${(error as Error).message}`);
      }

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/telemetry/index.ts (1)

122-145: ⚠️ Potential issue | 🟠 Major

noticeSeen is persisted even when the notice was suppressed, so the user may never see it.

When silent is true (JSON mode), the notice is correctly suppressed, but line 141 still marks noticeSeen: true. If the user's first interaction with the CLI happens to use --json, they will never be shown the telemetry notice on subsequent non-JSON runs. This is a privacy/compliance concern — users should be informed about data collection at least once.

Move the noticeSeen persistence inside the block that actually displays the notice.

Proposed fix
     if (!options.silent) {
       // Display notice
       console.log(
         'Note: OpenSpec collects anonymous usage stats. Opt out: OPENSPEC_TELEMETRY=0'
       );
-    }
 
-    // Mark as seen
-    await updateTelemetryConfig({ noticeSeen: true });
+      // Mark as seen only when actually displayed
+      await updateTelemetryConfig({ noticeSeen: true });
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/telemetry/index.ts` around lines 122 - 145, The function
maybeShowTelemetryNotice currently marks noticeSeen via updateTelemetryConfig({
noticeSeen: true }) regardless of options.silent; move the persistence so it
only runs when the notice is actually displayed. Inside
maybeShowTelemetryNotice, after you check if (!options.silent) and after the
console.log that shows the notice, call updateTelemetryConfig({ noticeSeen: true
}); remove or skip the existing unconditional updateTelemetryConfig call so that
config.noticeSeen is only set when the notice was shown to the user. Ensure you
still await getTelemetryConfig() and preserve the early returns for
!isTelemetryEnabled() and config.noticeSeen.
src/cli/index.ts (1)

504-513: ⚠️ Potential issue | 🟠 Major

console.log() in the catch block emits an empty line to stdout in JSON mode, corrupting output.

ora's default stream is process.stderr, so ora().fail() is safe. However, the preceding console.log() (line 509) writes a bare newline to stdout. After adding --json, when newChangeCommand throws and rethrows, this catch block runs and stdout receives an empty line — any consumer doing JSON.parse(stdout) will throw.

Fix the catch block to branch on options.json:

🐛 Proposed fix
  .action(async (name: string, options: NewChangeOptions) => {
    try {
      await newChangeCommand(name, options);
    } catch (error) {
-     console.log();
-     ora().fail(`Error: ${(error as Error).message}`);
+     if (options.json) {
+       console.log(JSON.stringify({ success: false, error: (error as Error).message }, null, 2));
+     } else {
+       console.log();
+       ora().fail(`Error: ${(error as Error).message}`);
+     }
      process.exit(1);
    }
  });

This also removes the need for new-change.ts to special-case the error path, so the throw error in its catch can remain. Note the same console.log() pattern exists in other commands' catch blocks (status, instructions, templates) — those may warrant the same treatment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/index.ts` around lines 504 - 513, The catch block in the action
handler for the CLI command prints a bare newline to stdout before calling
ora().fail, which corrupts JSON output; modify the action's catch to avoid
writing that newline when options.json is true (i.e., only call console.log()
when !options.json) and keep ora().fail((error as Error).message) for stderr
handling; apply the same pattern to the other command handlers that use the same
catch pattern (status, instructions, templates) so JSON output isn't polluted.
🧹 Nitpick comments (2)
openspec/changes/archive/2026-02-22-clean-json-output/design.md (1)

31-41: Design pattern differs slightly from the actual implementation.

The documented pattern guards spinner.stop() with if (!options.json) inside a finally block, but the actual commands call spinner.stop() unconditionally in both the success and catch paths (no finally). The implementation is fine since ora's stop() is safe on an unstarted spinner, but you may want to reconcile the doc to match what was actually shipped—or vice versa—so future contributors follow a single pattern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openspec/changes/archive/2026-02-22-clean-json-output/design.md` around lines
31 - 41, Documentation and implementation diverge: the doc shows guarding
spinner.stop() with if (!options.json) inside a finally, but the shipped code
calls spinner.stop() unconditionally in success and catch paths; reconcile them
by updating the design.md snippet (or the implementation) so both match. Either
change the design.md example to show the actual pattern using ora spinner with
unconditional spinner.stop() (noting ora.stop() is safe when not started), or
change the implementation to use a single try/finally block that only stops the
spinner when !options.json; update references to spinner, options.json, ora,
try/finally, and spinner.stop() accordingly so future readers and code follow
the same pattern.
openspec/specs/machine-readable-output/spec.md (1)

27-34: Error scenario leaves stdout contract undefined — consider specifying it.

The scenario declares that on failure: exit code is non-zero and the error goes to stderr. It doesn't state what stdout contains (empty, or a { success: false, error: "..." } JSON object). Machine-readable consumers parsing stdout will get an empty line (see src/cli/index.ts catch block below), which fails JSON.parse. Either document that stdout is empty on error, or prescribe a structured JSON error body on stdout for consistency with the success path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openspec/specs/machine-readable-output/spec.md` around lines 27 - 34, The
spec's error scenario is underspecified about stdout on failure; update the
spec.md "3. Error Handling" scenario to explicitly state that when a command
fails with --json the stdout MUST be empty (no JSON) and the error text MUST be
written to stderr with a non-zero exit code; then update the implementation
referenced (the catch block in src/cli/index.ts) to stop emitting any stdout
content on error and instead write the error message only to stderr and exit
non-zero so machine-readable consumers won't receive broken/empty JSON from
stdout.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@openspec/changes/archive/2026-02-22-clean-json-output/specs/machine-readable-output/spec.md`:
- Line 1: The header "Capability: Machine Readable Output" should be
hyphenated—replace the title line so it reads "Machine-Readable Output" (i.e.,
change the header text containing "Capability: Machine Readable Output" to use
"Machine-Readable Output") and apply the identical correction to the canonical
spec copy as well as this archive copy so both titles match exactly.

In `@openspec/specs/machine-readable-output/spec.md`:
- Line 1: The heading "Machine Readable Output" should use a hyphen as a
compound modifier; update the heading text (the top-level title string "#
Capability: Machine Readable Output") to "# Capability: Machine-Readable Output"
so the compound adjective is hyphenated consistently throughout the spec.

In `@src/cli/index.ts`:
- Around line 77-82: The telemetry notice is being marked seen even when
silenced by --json; modify maybeShowTelemetryNotice so that
updateTelemetryConfig({ noticeSeen: true }) is only called inside the branch
that displays the notice (i.e., only when options.silent is false).
Specifically, in the maybeShowTelemetryNotice function move the
updateTelemetryConfig call into the if (!options.silent) block (or add the
conditional around it) and remove any unconditional updateTelemetryConfig
invocation so noticeSeen is not set when the notice is suppressed.

In `@src/commands/workflow/new-change.ts`:
- Around line 70-75: In the catch block where you currently call spinner.fail
and then rethrow (checking options.json and using spinner.fail(`Failed to create
change '${name}'`)), ensure that when options.json is true you emit a structured
JSON error object to stdout (e.g., { error: true, message: error.message,
details: ... }) and do not rethrow; specifically, replace the throw error path
for options.json === true with a console.log of the JSON error payload and a
graceful return (or process.exit with non-zero status) so the upstream handler
doesn't print an empty line and JSON consumers receive a valid error body, while
keeping spinner.fail + throw for non-JSON mode.

---

Outside diff comments:
In `@src/cli/index.ts`:
- Around line 504-513: The catch block in the action handler for the CLI command
prints a bare newline to stdout before calling ora().fail, which corrupts JSON
output; modify the action's catch to avoid writing that newline when
options.json is true (i.e., only call console.log() when !options.json) and keep
ora().fail((error as Error).message) for stderr handling; apply the same pattern
to the other command handlers that use the same catch pattern (status,
instructions, templates) so JSON output isn't polluted.

In `@src/telemetry/index.ts`:
- Around line 122-145: The function maybeShowTelemetryNotice currently marks
noticeSeen via updateTelemetryConfig({ noticeSeen: true }) regardless of
options.silent; move the persistence so it only runs when the notice is actually
displayed. Inside maybeShowTelemetryNotice, after you check if (!options.silent)
and after the console.log that shows the notice, call updateTelemetryConfig({
noticeSeen: true }); remove or skip the existing unconditional
updateTelemetryConfig call so that config.noticeSeen is only set when the notice
was shown to the user. Ensure you still await getTelemetryConfig() and preserve
the early returns for !isTelemetryEnabled() and config.noticeSeen.

---

Nitpick comments:
In `@openspec/changes/archive/2026-02-22-clean-json-output/design.md`:
- Around line 31-41: Documentation and implementation diverge: the doc shows
guarding spinner.stop() with if (!options.json) inside a finally, but the
shipped code calls spinner.stop() unconditionally in success and catch paths;
reconcile them by updating the design.md snippet (or the implementation) so both
match. Either change the design.md example to show the actual pattern using ora
spinner with unconditional spinner.stop() (noting ora.stop() is safe when not
started), or change the implementation to use a single try/finally block that
only stops the spinner when !options.json; update references to spinner,
options.json, ora, try/finally, and spinner.stop() accordingly so future readers
and code follow the same pattern.

In `@openspec/specs/machine-readable-output/spec.md`:
- Around line 27-34: The spec's error scenario is underspecified about stdout on
failure; update the spec.md "3. Error Handling" scenario to explicitly state
that when a command fails with --json the stdout MUST be empty (no JSON) and the
error text MUST be written to stderr with a non-zero exit code; then update the
implementation referenced (the catch block in src/cli/index.ts) to stop emitting
any stdout content on error and instead write the error message only to stderr
and exit non-zero so machine-readable consumers won't receive broken/empty JSON
from stdout.

@@ -0,0 +1,34 @@
# Capability: Machine Readable Output
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Same hyphenation nit as the canonical spec.

Title should be Machine-Readable Output. Based on learnings, this delta creates the spec from scratch, so the correction should be applied identically in both this archive copy and openspec/specs/machine-readable-output/spec.md.

🧰 Tools
🪛 LanguageTool

[grammar] ~1-~1: Use a hyphen to join words.
Context: # Capability: Machine Readable Output ## Purpose Allow autom...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@openspec/changes/archive/2026-02-22-clean-json-output/specs/machine-readable-output/spec.md`
at line 1, The header "Capability: Machine Readable Output" should be
hyphenated—replace the title line so it reads "Machine-Readable Output" (i.e.,
change the header text containing "Capability: Machine Readable Output" to use
"Machine-Readable Output") and apply the identical correction to the canonical
spec copy as well as this archive copy so both titles match exactly.

@@ -0,0 +1,34 @@
# Capability: Machine Readable Output
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Machine-Readable Output — add hyphen to compound modifier.

"Machine Readable Output" should be "Machine-Readable Output" when used as a compound modifier (title/heading).

📝 Proposed fix
-# Capability: Machine Readable Output
+# Capability: Machine-Readable Output
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Capability: Machine Readable Output
# Capability: Machine-Readable Output
🧰 Tools
🪛 LanguageTool

[grammar] ~1-~1: Use a hyphen to join words.
Context: # Capability: Machine Readable Output ## Purpose Allow autom...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openspec/specs/machine-readable-output/spec.md` at line 1, The heading
"Machine Readable Output" should use a hyphen as a compound modifier; update the
heading text (the top-level title string "# Capability: Machine Readable
Output") to "# Capability: Machine-Readable Output" so the compound adjective is
hyphenated consistently throughout the spec.

Comment on lines +77 to +82
// Check if action command is requesting JSON output
const actionOpts = actionCommand.opts();
const isJson = actionOpts.json === true;

// Show first-run telemetry notice (if not seen)
await maybeShowTelemetryNotice();
await maybeShowTelemetryNotice({ silent: isJson });
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Telemetry notice is permanently suppressed for users whose first run uses --json.

maybeShowTelemetryNotice({ silent: true }) does not display the notice, but looking at src/telemetry/index.ts (lines 136–143), updateTelemetryConfig({ noticeSeen: true }) is called unconditionally — it runs outside the if (!options.silent) guard. On a first run with --json, the notice is silenced but noticeSeen is set to true, so the notice is never shown on any subsequent non-JSON invocation either. Users who start with --json are never informed about telemetry collection.

Fix in src/telemetry/index.ts:

// In maybeShowTelemetryNotice:
if (!options.silent) {
  console.log(
    'Note: OpenSpec collects anonymous usage stats. Opt out: OPENSPEC_TELEMETRY=0'
  );
  await updateTelemetryConfig({ noticeSeen: true }); // ← move inside this block
}
// Remove the unconditional call below
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/index.ts` around lines 77 - 82, The telemetry notice is being marked
seen even when silenced by --json; modify maybeShowTelemetryNotice so that
updateTelemetryConfig({ noticeSeen: true }) is only called inside the branch
that displays the notice (i.e., only when options.silent is false).
Specifically, in the maybeShowTelemetryNotice function move the
updateTelemetryConfig call into the if (!options.silent) block (or add the
conditional around it) and remove any unconditional updateTelemetryConfig
invocation so noticeSeen is not set when the notice is suppressed.

Comment on lines 70 to 75
} catch (error) {
spinner.fail(`Failed to create change '${name}'`);
if (!options.json) {
spinner.fail(`Failed to create change '${name}'`);
}
throw error;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Error path in JSON mode doesn't emit a structured error body before rethrowing.

When options.json is true, the function skips spinner.fail but still rethrows. The upstream catch block in src/cli/index.ts then executes console.log(), writing an empty line to stdout before calling ora().fail() on stderr. Any tool parsing stdout as JSON will fail — the spec's Requirement 3 is violated in the error case.

The cleanest fix is in the new change action handler in src/cli/index.ts (see the comment there), but an alternative is to fully resolve the error here without rethrowing in JSON mode:

🐛 Alternative fix (handle JSON error locally)
  } catch (error) {
-   if (!options.json) {
-     spinner.fail(`Failed to create change '${name}'`);
-   }
-   throw error;
+   if (options.json) {
+     console.log(JSON.stringify({ success: false, error: (error as Error).message }, null, 2));
+     process.exitCode = 1;
+     return;
+   }
+   spinner.fail(`Failed to create change '${name}'`);
+   throw error;
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (error) {
spinner.fail(`Failed to create change '${name}'`);
if (!options.json) {
spinner.fail(`Failed to create change '${name}'`);
}
throw error;
}
} catch (error) {
if (options.json) {
console.log(JSON.stringify({ success: false, error: (error as Error).message }, null, 2));
process.exitCode = 1;
return;
}
spinner.fail(`Failed to create change '${name}'`);
throw error;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/workflow/new-change.ts` around lines 70 - 75, In the catch block
where you currently call spinner.fail and then rethrow (checking options.json
and using spinner.fail(`Failed to create change '${name}'`)), ensure that when
options.json is true you emit a structured JSON error object to stdout (e.g., {
error: true, message: error.message, details: ... }) and do not rethrow;
specifically, replace the throw error path for options.json === true with a
console.log of the JSON error payload and a graceful return (or process.exit
with non-zero status) so the upstream handler doesn't print an empty line and
JSON consumers receive a valid error body, while keeping spinner.fail + throw
for non-JSON mode.


## Requirements

### 1. Spinner Suppression
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we switch these numbered sections to the standard requirement format? Using headings like ### Requirement: ... makes this consistent with OpenSpec conventions and easier for tooling to parse.


#### Scenario: First run with JSON
- **Given** a new environment where the telemetry notice hasn't been shown
- **When** running `openspec list --json`
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with suppressing the notice in JSON mode. Can we make deferred notice behavior explicit here too: suppress on JSON runs, do not mark noticeSeen yet, and show it on the first later non-JSON run. That keeps JSON clean and still guarantees user disclosure.


### New Capabilities

- `machine-readable-output`: Ensures all CLI commands providing a `--json` flag produce clean, parseable stdout without terminal visual effects or side-channel messages.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice fix direction. One scope detail: this looks like a modification of existing capabilities, not a brand new one. We are changing telemetry notice behavior and workflow command output behavior. Could we move this to Modified Capabilities and list telemetry plus cli-artifact-workflow?

- **Then** the telemetry notice is NOT printed to stdout
- **And** only the JSON change list is printed

### 3. Error Handling
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we define stdout behavior on failure more explicitly for JSON mode? Suggest: stdout stays empty, error text goes to stderr, and exit code is non-zero. That gives consumers a clear contract.

}

// Mark as seen
await updateTelemetryConfig({ noticeSeen: true });
Copy link
Contributor

Choose a reason for hiding this comment

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

Small logic issue here: noticeSeen is saved even when silent mode suppresses the notice. If a user starts with --json, they may never see the disclosure. Can we move this update inside the if (!options.silent) block?

@@ -0,0 +1,34 @@
# Capability: Machine Readable Output
Copy link
Contributor

Choose a reason for hiding this comment

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

This archived change spec looks like a full future-state document. Current conventions for change specs use delta sections like ## ADDED Requirements and ## MODIFIED Requirements. Could we convert this to delta format so it stays consistent with tooling and other archived changes?

.description('Create a new change directory')
.option('--description <text>', 'Description to add to README.md')
.option('--schema <name>', `Workflow schema to use (default: ${DEFAULT_SCHEMA})`)
.option('--json', 'Output as JSON (success/failure details)')
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice addition of --json here. One related fix is still needed in the catch block just below: it currently does console.log() before ora().fail(...). In JSON mode that writes to stdout and can break parsers. Can we guard the spacer with if (!options.json) and keep error output on stderr only?

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.

2 participants