You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(status): exit gracefully when no changes exist (Fission-AI#759)
* fix(status): exit gracefully when no changes exist (Fission-AI#714)
Extract `getAvailableChanges` as a public function from `validateChangeExists`
and use it in `statusCommand` to detect the no-changes case early. Returns a
friendly message (text and JSON modes) with exit code 0 instead of a fatal error.
Generated with Claude Code using claude-opus-4-6.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: fix design risk description and proposal accuracy
Address CodeRabbit review feedback:
- Fix contradictory risk description in design.md (double-read happens
when changes exist, not when they don't)
- Clarify in proposal.md that validateChangeExists was internally
refactored to delegate to getAvailableChanges
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(status): narrow catch in getAvailableChanges to ENOENT only
Return [] only when the changes directory doesn't exist (ENOENT).
Rethrow other errors (EACCES, etc.) so real filesystem issues
surface instead of being silently masked as "no changes".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Tabish Bidiwale <30385142+TabishB@users.noreply.github.com>
`statusCommand` in `src/commands/workflow/status.ts` calls `validateChangeExists()` from `shared.ts` as its first operation. When no `--change` option is provided and no change directories exist, `validateChangeExists` throws: `No changes found. Create one with: openspec new change <name>`. This error propagates up as a fatal CLI error (non-zero exit code).
4
+
5
+
This is correct behavior for commands like `apply` and `show` that require a change to operate on. However, `status` is an informational command — it should report the current state, even when that state is "no changes exist."
6
+
7
+
The error surfaces during onboarding (issue #714) when AI agents call `openspec status` before any change has been created.
8
+
9
+
## Goals / Non-Goals
10
+
11
+
**Goals:**
12
+
- Make `openspec status` exit with code 0 and a friendly message when no changes exist
13
+
- Support both text and JSON output modes for the no-changes case
14
+
- Keep all other commands' validation behavior unchanged
15
+
16
+
**Non-Goals:**
17
+
- Changing the behavior of `validateChangeExists` (keep it strict for all consumers; only extract its internal helper)
18
+
- Changing the onboard template or skill instructions
19
+
- Handling the case where `--change` is provided but the specific change doesn't exist (this should remain an error)
20
+
21
+
## Decisions
22
+
23
+
### Extract `getAvailableChanges` and check before validation
24
+
25
+
**Rationale**: Extract the private `getAvailableChanges` closure from `validateChangeExists` into a public exported function in `shared.ts`. Then, in `statusCommand`, call `getAvailableChanges`*before*`validateChangeExists` to detect the no-changes case early and handle it gracefully. This avoids using try/catch for control flow and eliminates any coupling to error message strings.
26
+
27
+
**Alternative considered**: Catching the error from `validateChangeExists` by matching `error.message.startsWith('No changes found')`. Rejected because string coupling is fragile — if the error message changes, the catch silently stops working.
28
+
29
+
**Alternative considered**: Adding a `throwOnEmpty` parameter to `validateChangeExists`. Rejected because it adds complexity to a shared function for a single consumer's needs and mixes UX concerns into a validation utility.
30
+
31
+
### Keep `validateChangeExists` strict
32
+
33
+
**Rationale**: `validateChangeExists` remains unchanged in behavior — it still throws for all error cases. The graceful handling lives entirely in `statusCommand`, which is the appropriate layer for UX decisions. Other commands (`apply`, `show`, `instructions`) are unaffected.
34
+
35
+
## Risks / Trade-offs
36
+
37
+
-[Risk] Extra filesystem read when no `--change` is provided and changes *do* exist (`getAvailableChanges` is called first, then `validateChangeExists` performs its own read) → Mitigation: `statusCommand` returns early before reaching `validateChangeExists` when no changes exist, so the double-read only occurs when changes are present — minimal overhead.
38
+
-[Risk] Other commands may also benefit from graceful no-changes handling in the future → Mitigation: `getAvailableChanges` is now public and reusable, making it easy to apply the same pattern elsewhere.
When `openspec status` is called without `--change` and no changes exist (e.g., during onboarding on a freshly initialized project), the CLI throws a fatal error: `No changes found. Create one with: openspec new change <name>`. This breaks the onboarding flow because AI agents may call `openspec status` before any change has been created, causing the agent to halt or report failure. Fixes [#714](https://github.com/Fission-AI/OpenSpec/issues/714).
4
+
5
+
## What Changes
6
+
7
+
-`openspec status` will exit gracefully (code 0) with a friendly message when no changes exist, instead of throwing a fatal error
8
+
-`openspec status --json` will return a valid JSON object with an empty changes array when no changes exist
9
+
- Other commands (`apply`, `show`, etc.) retain their current strict validation behavior
10
+
11
+
## Capabilities
12
+
13
+
### New Capabilities
14
+
15
+
-`graceful-status-empty`: Graceful handling of `openspec status` when no changes exist, covering both text and JSON output modes
16
+
17
+
### Modified Capabilities
18
+
19
+
_None — `validateChangeExists` was internally refactored to delegate to the newly exported `getAvailableChanges`, but its behavior and public contract are unchanged. Other consumers are unaffected._
20
+
21
+
## Impact
22
+
23
+
-`src/commands/workflow/shared.ts` — extract `getAvailableChanges` as a public function (validation behavior unchanged)
24
+
-`src/commands/workflow/status.ts` — check for available changes before validation, handle empty case gracefully
25
+
- Tests for the status command need to cover the new graceful behavior
### Requirement: Status command exits gracefully when no changes exist
4
+
The `statusCommand` function SHALL check for available changes via `getAvailableChanges` before calling `validateChangeExists`. When no `--change` option is provided and no change directories exist, it SHALL print a friendly informational message and exit with code 0, instead of reaching `validateChangeExists` and propagating a fatal error.
5
+
6
+
#### Scenario: No changes exist, text mode
7
+
-**WHEN** user runs `openspec status` without `--change` and no change directories exist under `openspec/changes/`
8
+
-**THEN** the CLI prints `No active changes. Create one with: openspec new change <name>` to stdout and exits with code 0
9
+
10
+
#### Scenario: No changes exist, JSON mode
11
+
-**WHEN** user runs `openspec status --json` without `--change` and no change directories exist
12
+
-**THEN** the CLI outputs `{"changes":[],"message":"No active changes."}` as valid JSON to stdout and exits with code 0
13
+
14
+
### Requirement: Existing status validation behavior is preserved
15
+
Other error paths in `validateChangeExists` that apply to the status command SHALL continue to throw errors as before. Commands other than `status` that use `validateChangeExists` SHALL NOT be affected.
16
+
17
+
#### Scenario: Changes exist but --change not specified
18
+
-**WHEN** user runs `openspec status` without `--change` and one or more change directories exist
19
+
-**THEN** the CLI throws an error listing available changes with the message `Missing required option --change. Available changes: ...`
20
+
21
+
#### Scenario: Specified change does not exist
22
+
-**WHEN** user runs `openspec status --change non-existent`
23
+
-**THEN** the CLI throws an error with message `Change 'non-existent' not found`
24
+
25
+
#### Scenario: Other commands unaffected
26
+
-**WHEN** user runs `openspec show` or `openspec instructions` without `--change` and no changes exist
27
+
-**THEN** the CLI throws the original `No changes found` error (no behavior change)
0 commit comments