Skip to content

fix: clarify invalid config JSON errors#118

Open
nightcityblade wants to merge 6 commits into
404-PF:mainfrom
nightcityblade:fix/issue-102
Open

fix: clarify invalid config JSON errors#118
nightcityblade wants to merge 6 commits into
404-PF:mainfrom
nightcityblade:fix/issue-102

Conversation

@nightcityblade
Copy link
Copy Markdown
Contributor

@nightcityblade nightcityblade commented Jun 6, 2026

Fixes #102

Summary

  • Catch malformed JSON in loadConfig and wrap it with a clearer CLI-facing message
  • Include the config file path and a hint to fix syntax or rerun init
  • Add a regression test for malformed config JSON

Tests

  • npm test
  • node --test tests/*.test.mjs
  • npm run build
  • npx prettier --check src/config/store.ts
  • git diff --check

Note: npm run format:check currently reports pre-existing formatting warnings in unrelated files (src/commands/init.ts, src/commands/suggest.ts, src/git/hook.ts, src/llm/client.ts); changed TypeScript file passes Prettier.

Summary by CodeRabbit

  • New Features

    • Added batch command to process multiple Git repositories at once
    • Added --stream flag for real-time suggestion streaming with OpenAI and Anthropic providers
    • Added --yes/--auto flags for automatic commits using the first suggestion
  • Bug Fixes

    • Improved error messages when configuration files contain invalid JSON
  • Documentation

    • Updated README with streaming and batch processing workflow examples

Copy link
Copy Markdown
Contributor

@404-Page-Found 404-Page-Found left a comment

Choose a reason for hiding this comment

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

PR #118 Review: fix: clarify invalid config JSON errors

Overview

Author @nightcityblade
Base → Head mainfix/issue-102
Size +59/−1 across 2 files (1 commit)
Linked issue #102 — "Improve invalid config JSON error message with file path"

Scope & purpose: Wraps JSON.parse in loadConfig() with a try/catch that rethrows SyntaxError as a user-friendly error including the config file path and a fix hint (commit-echo init). Adds one regression test. Clean, focused, single-concern PR.


✅ What's good

  1. Meets every acceptance criterion from #102:

    • Catches JSON.parse failures and throws a user-friendly message
    • Includes the config file path in the error message
    • Non-JSON errors (e.g., permission errors) are rethrown unchanged
    • Regression test validates the full error message content
  2. Preserves the original error via { cause: error } — the SyntaxError is still accessible in the error chain for debugging.

  3. Correct error discriminationinstanceof SyntaxError guard ensures only JSON parse errors get wrapped; anything else rethrows cleanly.

  4. Test is thorough — validates three distinct parts of the error message (prefix, config path, hint), uses escapeRegExp for the dynamic path, and cleans up environment variables in a finally block.

  5. Prettier clean, builds successfully, all 83 tests pass (1 skipped — same as main minus streaming tests added after the branch point). No regressions.

  6. Commit message follows conventional commits (fix: clarify invalid config JSON errors).


⚠️ Issues

Important — Test not hermetic on Windows

The test manipulates HOME and XDG_CONFIG_HOME to redirect getConfigDir() to a temp directory. This works on Linux/macOS, but on Windows the getConfigDir() function takes the win32 branch first and uses APPDATA, which the test never overrides:

// src/config/store.ts — getConfigDir()
if (os === 'win32') {
  const appData = process.env['APPDATA'];
  if (appData) return join(appData, 'commit-echo'); // ← always taken on Windows
}

Impact: On Windows, the test writes "{ invalid json" to the real user's %APPDATA%/commit-echo/config.json instead of a temp directory. The finally block restores env vars but never restores the original file contents, so running npm test on Windows would silently corrupt the developer's config.

Suggestion: Override APPDATA in the test (or mock getConfigPath directly). Something like:

const originalAppData = process.env.APPDATA;
process.env.APPDATA = home;
// ... run test ...
process.env.APPDATA = originalAppData;

Or, to avoid env var manipulation entirely, mock getConfigPath / getConfigDir to return the temp path directly. This would make the test platform-agnostic.

Note: CI runs on Linux, so this won't break CI — but it will break local npm test for any Windows contributor.


Nitpick — Minor observations

  1. Double-quoted strings in the test — The test uses double quotes ("node:assert/strict") while the project convention (visible in .prettierrc with singleQuote: true and in all other test files) is single quotes. This doesn't fail Prettier because Prettier is not configured for the .mjs test files, but it's a minor style inconsistency.

Verdict: Approve ✅

The PR is well-scoped, correct for its target platform (Linux/macOS CI), and cleanly implemented. The Windows test hermeticity issue is real but does not block merge — it's a pre-existing platform gap that affects any test relying on HOME/XDG_CONFIG_HOME on Windows. A follow-up issue to harden tests for Windows would be the appropriate place to address it.

The core change in src/config/store.ts is solid and ready to ship.

Copy link
Copy Markdown
Contributor

@404-Page-Found 404-Page-Found left a comment

Choose a reason for hiding this comment

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

PR #118 Review: fix: clarify invalid config JSON errors

Overview

Author @nightcityblade
Base → Head mainfix/issue-102
Size +59/−1 across 2 files (1 commit)
Linked issue #102 — "Improve invalid config JSON error message with file path"

Scope & purpose: Wraps JSON.parse in loadConfig() with a try/catch that rethrows SyntaxError as a user-friendly error including the config file path and a fix hint (commit-echo init). Adds one regression test. Clean, focused, single-concern PR.


✅ What's good

  1. Meets every acceptance criterion from #102:

    • Catches JSON.parse failures and throws a user-friendly message
    • Includes the config file path in the error message
    • Non-JSON errors (e.g., permission errors) are rethrown unchanged
    • Regression test validates the full error message content
  2. Preserves the original error via { cause: error } — the SyntaxError is still accessible in the error chain for debugging.

  3. Correct error discriminationinstanceof SyntaxError guard ensures only JSON parse errors get wrapped; anything else rethrows cleanly.

  4. Test is thorough — validates three distinct parts of the error message (prefix, config path, hint), uses escapeRegExp for the dynamic path, and cleans up environment variables in a finally block.

  5. Prettier clean, builds successfully, all 83 tests pass (1 skipped — same as main minus streaming tests added after the branch point). No regressions.

  6. Commit message follows conventional commits (fix: clarify invalid config JSON errors).


🚫 Blocking — Test corrupts real config on Windows

The test manipulates HOME and XDG_CONFIG_HOME to redirect getConfigDir() to a temp directory. This works on Linux/macOS, but on Windows the getConfigDir() function takes the win32 branch first and uses APPDATA, which the test never overrides:

// src/config/store.ts — getConfigDir()
if (os === 'win32') {
  const appData = process.env['APPDATA'];
  if (appData) return join(appData, 'commit-echo'); // ← always taken on Windows
}

Impact: On Windows, the test writes "{ invalid json" to the real user's %APPDATA%/commit-echo/config.json instead of a temp directory. The finally block restores env vars but never restores the original file contents, so running npm test on Windows would silently corrupt the developer's config.

Fix: Add APPDATA override to the env-var backup/restore block so the test is hermetic on all platforms:

const originalHome = process.env.HOME;
const originalXdgConfigHome = process.env.XDG_CONFIG_HOME;
const originalAppData = process.env.APPDATA;
const home = await mkdtemp(`${tmpdir()}/commit-echo-config-`);

process.env.HOME = home;
process.env.APPDATA = home;       // ← needed on Windows
delete process.env.XDG_CONFIG_HOME;
// ... test body ...
} finally {
  if (originalHome === undefined) {
    delete process.env.HOME;
  } else {
    process.env.HOME = originalHome;
  }
  if (originalAppData === undefined) {
    delete process.env.APPDATA;
  } else {
    process.env.APPDATA = originalAppData;
  }
  if (originalXdgConfigHome === undefined) {
    delete process.env.XDG_CONFIG_HOME;
  } else {
    process.env.XDG_CONFIG_HOME = originalXdgConfigHome;
  }
}

CI runs on Linux so this won't break the pipeline, but it will silently destroy the local config of any Windows contributor who runs npm test.


Verdict: Request changes 🚫

The core loadConfig() change is solid and ready to ship. The test must be updated to also override APPDATA on Windows (or mock getConfigPath directly) to prevent corrupting the developer's real config file. Once that's addressed, this is good to merge.

404-Page-Found and others added 5 commits June 6, 2026 23:03
* feat: add streaming support for commit suggestions

* fix: add error handling to streaming path and clean up code quality issues

- Wrap streaming for-await in try/catch with user-friendly error message
- Remove redundant truncateDiff call in streaming branch (already done inside generateSuggestionsStream)
- Consolidate duplicate parseSuggestions/truncateDiff imports into single line
- Use const instead of let for never-reassigned model variable

* feat: enhance streaming support for commit suggestions with incremental output

- Introduced `--stream` option in README for real-time suggestion output.
- Updated `generateSuggestionsStream` to yield meta events and text chunks.
- Improved error handling and parsing for both OpenAI and Anthropic providers.
- Added new utility functions for parsing SSE lines and handling state.
- Included tests for streaming functionality and parsing logic.

* feat: implement streaming support validation and enhance error handling

- Added `assertStreamingSupported` function to check if the provider supports streaming.
- Integrated streaming support validation in the `suggestCommand` function to provide user feedback for unsupported providers.
- Updated tests to cover streaming functionality and validation for various providers.
- Ensured truncation warnings are displayed correctly in both streaming and non-streaming modes.

* feat: enhance streaming support with model event handling

- Introduced `ProviderStreamChunk` type to encapsulate streaming events for text and model updates.
- Updated `completeStream` methods in providers to yield `ProviderStreamChunk` instead of raw strings.
- Enhanced `generateSuggestionsStream` to handle model events and update the model dynamically during streaming.
- Refactored request body construction in providers to support streaming options.
- Added tests to verify model event extraction from streaming responses and ensure correct handling in SSE parsing.

* feat: enhance OpenAI streaming handling with [DONE] support and refactor tests

- Added handling for the [DONE] signal in the OpenAI-compatible provider's streaming logic to gracefully terminate the stream.
- Refactored tests to utilize a new `streamFromChunks` helper for improved readability and maintainability.
- Introduced new tests to validate the handling of final buffer states and ensure correct parsing of streamed output.

* feat: enhance streaming suggestions with auto-commit support

- Updated README to include `--yes` option for non-interactive streaming that auto-commits the first suggestion.
- Refactored `suggestCommand` to support global options for auto-commit.
- Enhanced streaming logic to handle provider-specific streaming capabilities.
- Added tests for streaming output with Anthropic provider and auto-commit functionality.

* refactor: unify stream event naming and consolidate truncation warning

Rename SuggestionStreamEvent kind 'chunk' to 'text' to align with
ProviderStreamChunk at the provider layer. Consolidate the truncation
warning into a single code path shared by streaming and non-streaming
modes.

* refactor: extract shared SSE reader and fix releaseLock safety

- Add streamSseResponse() helper in sse.ts to eliminate duplicated
  reader-loop boilerplate across OpenAI and Anthropic providers
- Guard reader.releaseLock() with a cancelled flag to prevent errors
  when reader.cancel() was already called
- Replace parseAnthropicSseLines generator (boolean return type) with
  parseAnthropicSseLine line-at-a-time function and SSE_STREAM_END
  sentinel for cleaner control flow
- Rename assertStreamingSupported to getStreamingProvider to better
  reflect that it creates and returns a provider

* refactor: remove dead duplicate SSE_STREAM_END check in streamSseResponse

When done is true, buffer is always empty (set on the line before the
for-loop), so the trailing buffer-flush block was unreachable dead code.
Simplify to break immediately after the for-loop when done, since the
final line is already captured in lines when lines.pop() is skipped.
Co-authored-by: Peter7896 <265430982+Peter7896@users.noreply.github.com>
# Conflicts:
#	src/index.ts

Co-authored-by: Peter7896 <265430982+Peter7896@users.noreply.github.com>
* feat: add interactive multi-repo batch mode (404-PF#6)

Add a  command that scans a directory for
git repositories, identifies those with staged changes, and processes each
one in sequence — generating suggestions, committing, and reporting results.

-  finds all git repos in ./projects
- Processes repos with staged changes in sequence
- Supports --yes for unattended batch operation (auto-commit first suggestion)
- Supports --recursive to search subdirectories for nested repos
- Prints clear summary of results per repo (succeeded/skipped/failed)
- Skips repos without staged changes with a notice
- 13 unit tests covering repo discovery, change detection, and diff retrieval

Closes 404-PF#6

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* perf: hoist buildProfile call outside per-repo loop in batch command

buildProfile(config.historySize) was called inside the per-repository
loop, causing redundant file I/O (reading + parsing the full commit
history file) once per repo. Move it before the loop so it executes
once, and the cached profile is reused across all repos.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: address CodeRabbit review findings on batch command

- check if rootDir itself is a git repo before scanning children
- bump getGitDiff maxBuffer to 100 MB for large monorepo diffs
- wrap readdirSync with try/catch to skip unreadable directories
- relax gitCommit regex to match detached HEAD output
- add optional body prompt in interactive batch mode (consistent UX)
- export gitCommit and add unit tests (message, body, empty commit)
- add tests for rootDir-as-repo edge case

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: relax overly strict git hash length assertion in batch test

assert.equal(result.hash.length, 7) was brittle — Git abbreviated hashes
can exceed 7 characters depending on repository size. Replace with a hex
regex check and a minimum-length assertion (≥7), which matches Git's
shortest-prefix behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@nightcityblade
Copy link
Copy Markdown
Contributor Author

Thanks — I addressed the Windows hermeticity issue on this branch.

What changed:

  • added an APPDATA override alongside HOME in tests/config-store.test.mjs
  • restored the original APPDATA value in the finally block, so the test no longer risks touching a contributor's real %APPDATA%/commit-echo/config.json on Windows

Validation:

  • npm run build
  • node --test tests/config-store.test.mjs

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 6, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds streaming support for real-time commit suggestion generation and introduces a new batch command for processing multiple Git repositories with automated LLM-based commits. Streaming works with OpenAI-compatible and Anthropic providers via server-sent events, while batch discovers repos, detects changes, generates suggestions, and auto-commits or interactively confirms per repository.

Changes

Streaming Support for Suggestions

Layer / File(s) Summary
Stream type contracts and SSE infrastructure
src/types.ts, src/providers/sse.ts
Adds ProviderStreamChunk union type and optional Provider.completeStream method. Introduces streamSseResponse async generator for reading/buffering SSE lines and parsing functions for OpenAI (text/model/done/error) and Anthropic (stateful event tracking) formats.
Provider streaming implementations
src/providers/openai-compatible.ts, src/providers/anthropic.ts, src/providers/index.ts
Refactors OpenAI and Anthropic providers to centralize request-body construction and add streaming POST via completeStream methods. Exports getStreamingProvider (validates provider supports streaming) and completeStream orchestrator that routes to provider implementations.
LLM client streaming layer
src/llm/client.ts
Introduces SuggestionStreamEvent type and generateSuggestionsStream async generator that yields truncation metadata, model name, and text chunks from provider streaming for caller to parse into Suggestion objects.
Suggest command streaming integration
src/commands/suggest.ts, src/index.ts, README.md
Extends suggestCommand with stream option to support two generation paths: streaming mode (resolves provider, consumes events, displays text incrementally) and non-streaming (spinner-based). Wires --stream CLI flag, merges with global options, and documents usage/provider support.
Streaming tests
tests/helpers/stream-from-chunks.mjs, tests/client-stream.test.mjs, tests/stream-sse.test.mjs, tests/providers-index.test.mjs, tests/e2e/suggest-smoke.test.mjs
Adds test helpers for SSE simulation and unit coverage for OpenAI/Anthropic SSE parsing, generateSuggestionsStream event sequencing, provider completeStream integration, and e2e tests for custom provider SSE, Anthropic streaming, stream+auto-commit, unsupported provider rejection, and parse error handling.

Batch Repository Processing Command

Layer / File(s) Summary
Git utility functions
src/commands/batch.ts (utilities section)
Exports findGitRepositories for recursive directory scanning, gitHasChanges for staged/unstaged detection, getGitDiff for retrieving diffs, gitCommit for creating commits with optional bodies, and BatchResult interface for per-repo outcomes.
Batch command orchestration
src/commands/batch.ts (batchCommand)
Implements main workflow: initializes CLI, discovers repos, loads config once, validates API key, then loops per repo to check staged changes, fetch diffs, generate suggestions, auto-commit first suggestion (in --yes mode) or interactively select/edit body, write history, collect failures, and report batch summary.
CLI wiring and config error handling
src/index.ts, src/config/store.ts
Registers batch subcommand with optional directory, --recursive, and --yes/--auto flags, merging global options. Improves loadConfig() JSON error handling to include config path and recovery hints (point to commit-echo init). Extends CLI help with batch examples.
Batch tests and infrastructure
tests/batch.test.mjs, tests/config-store.test.mjs, tests/git-diff.test.mjs, tests/history-profile.test.mjs, tests/e2e/suggest-smoke.test.mjs
Unit tests for git utilities (discovery, change detection, diffs, commits), malformed JSON config error messages, and e2e tests for --auto (selecting first suggestion with/without committing). Updates test infrastructure to disable Git fsmonitor, resolve real temp paths, and support platform-aware config directories.

Sequence Diagram(s)

sequenceDiagram
  participant App as suggest command
  participant Provider as Streaming Provider
  participant LLM as LLM API
  App->>Provider: getStreamingProvider(config)
  Provider-->>App: validated provider
  App->>App: startSpinner
  App->>Provider: provider.completeStream(params)
  Provider->>LLM: POST /messages or /chat/completions with stream:true
  loop SSE Event Stream
    LLM-->>Provider: data: SSE line
    Provider->>Provider: parseLine (OpenAI/Anthropic)
    Provider-->>App: { kind: 'model', model: name }
    Provider-->>App: { kind: 'text', text: chunk }
  end
  App->>App: accumulate text + parse suggestions
  App->>App: displaySuggestions or skip for --stream
  App->>User: interactive select or auto-commit
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • 404-PF/commit-echo#121: Both PRs introduce the batch command feature by adding src/commands/batch.ts with git utilities and the batchCommand orchestrator, wiring it into src/index.ts, and covering it in tests/batch.test.mjs.

Poem

🐰 A streaming flutter of suggestions flows,
Batches of repos in a swift repose.
From git diffs born to commits with care,
Suggestions stream through the market air!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR contains substantial out-of-scope changes unrelated to issue #102: new batch command (381 LOC), streaming support (300+ LOC across multiple files), and extensive test coverage for unrelated features. Remove all changes unrelated to config JSON error handling (batch command, streaming functionality, and corresponding tests). Keep only config/store.ts, config-store.test.mjs, and minimal supporting changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 16.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 'fix: clarify invalid config JSON errors' directly aligns with the main objective to improve error messaging for malformed JSON in config files.
Linked Issues check ✅ Passed The PR successfully addresses all requirements from issue #102: catches JSON parse failures with clear messaging, includes config file path, preserves non-JSON error behavior, and adds regression test.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Copy link
Copy Markdown

@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: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Around line 190-195: The README incorrectly states that "commit-echo suggest
--stream --yes" will auto-commit; update the docs/examples to require the actual
commit flag by changing the example and wording to include "--commit" (i.e., use
"commit-echo suggest --stream --yes --commit") and clarify in the sentence that
suggest only performs a commit when the --commit flag is present; reference the
CLI command "suggest" and flags "--stream", "--yes", and "--commit" when making
the edit.

In `@src/commands/batch.ts`:
- Around line 295-324: The current try/catch around gitCommit(...) and
appendEntry(...) hides appendEntry failures as "Commit failed"; split the
operations so gitCommit(repoPath, first.message, first.body) is executed and
handled first (log success and push a success result using commitResult,
repoPath, repoName), then call appendEntry(...) in its own try/catch; if
appendEntry fails, log and record that the history append failed (not the
commit), and do not mark the commit itself as failed. Apply the same split logic
in the interactive commit block where gitCommit and appendEntry are currently
wrapped together.
- Around line 163-166: The current directory check uses existsSync(dir) then
statSync(dir).isDirectory(), but statSync can throw (e.g., permission denied)
even when existsSync returns true; wrap the statSync call in a try/catch (around
the block that calls statSync and checks isDirectory) so any thrown error is
caught, call outro(pc.red(`Directory not found: ${dir}`)) or a clearer message
including err.message, and return; specifically update the validation around
existsSync and statSync in the batch command to handle exceptions from statSync
and avoid crashing.
- Around line 64-74: The current try/catch around execSync('git diff --cached
--quiet', ...) and execSync('git diff --quiet', ...) treats any git error as
“changes exist”, which hides real failures; update the catch blocks in
src/commands/batch.ts to inspect the thrown error from execSync (the Error
object with a .status/exit code) and only set staged = true / unstaged = true
when the exit code indicates diffs were found (exit code 1); for any other error
(e.g., non-zero status other than 1 or missing repo/permission errors) rethrow
or propagate the original error so failures aren’t masked. Ensure you reference
the specific execSync calls and the staged/unstaged variables when making the
change.

In `@src/providers/index.ts`:
- Around line 62-70: In completeStream, validate that the resolved provider
actually supports streaming before invoking resolvedProvider.completeStream;
check whether resolvedProvider.completeStream is a function and if not, either
replace the injected provider with getStreamingProvider(configProvider) or
throw/return a clear CLI-friendly error. Update the logic in completeStream to
test the optional provider (and the result of getStreamingProvider) for a
completeStream method and only yield* from resolvedProvider.completeStream when
present, otherwise surface a descriptive error referring to completeStream and
getStreamingProvider so callers know a non-streaming Provider was supplied.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ba939be5-4f56-4e34-a244-8988e9142666

📥 Commits

Reviewing files that changed from the base of the PR and between 8cd56f0 and cc8b374.

📒 Files selected for processing (20)
  • README.md
  • src/commands/batch.ts
  • src/commands/suggest.ts
  • src/config/store.ts
  • src/index.ts
  • src/llm/client.ts
  • src/providers/anthropic.ts
  • src/providers/index.ts
  • src/providers/openai-compatible.ts
  • src/providers/sse.ts
  • src/types.ts
  • tests/batch.test.mjs
  • tests/client-stream.test.mjs
  • tests/config-store.test.mjs
  • tests/e2e/suggest-smoke.test.mjs
  • tests/git-diff.test.mjs
  • tests/helpers/stream-from-chunks.mjs
  • tests/history-profile.test.mjs
  • tests/providers-index.test.mjs
  • tests/stream-sse.test.mjs

Comment thread README.md
Comment on lines +190 to +195
Use `--stream` to print LLM output incrementally instead of waiting behind a spinner. Supported for OpenAI-compatible and Anthropic providers; use non-streaming mode for Cohere. Pair with `--yes` for a non-interactive workflow that streams output and auto-commits the first suggestion.

```bash
commit-echo suggest --stream
commit-echo suggest --stream --yes
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

commit-echo suggest --stream --yes does not auto-commit without --commit.

The new wording/examples say this combo auto-commits, but in the current CLI flow suggest only commits when --commit is set. Please update the text/example to avoid misleading users.

Suggested doc fix
-Use `--stream` to print LLM output incrementally instead of waiting behind a spinner. Supported for OpenAI-compatible and Anthropic providers; use non-streaming mode for Cohere. Pair with `--yes` for a non-interactive workflow that streams output and auto-commits the first suggestion.
+Use `--stream` to print LLM output incrementally instead of waiting behind a spinner. Supported for OpenAI-compatible and Anthropic providers; use non-streaming mode for Cohere. Pair with `--yes` for non-interactive selection, and add `--commit` if you also want auto-commit.
 
 ```bash
 commit-echo suggest --stream
-commit-echo suggest --stream --yes
+commit-echo suggest --stream --yes --commit
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **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.

```suggestion
Use `--stream` to print LLM output incrementally instead of waiting behind a spinner. Supported for OpenAI-compatible and Anthropic providers; use non-streaming mode for Cohere. Pair with `--yes` for non-interactive selection, and add `--commit` if you also want auto-commit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 190 - 195, The README incorrectly states that
"commit-echo suggest --stream --yes" will auto-commit; update the docs/examples
to require the actual commit flag by changing the example and wording to include
"--commit" (i.e., use "commit-echo suggest --stream --yes --commit") and clarify
in the sentence that suggest only performs a commit when the --commit flag is
present; reference the CLI command "suggest" and flags "--stream", "--yes", and
"--commit" when making the edit.

Comment thread src/commands/batch.ts
Comment on lines +64 to +74
try {
execSync('git diff --cached --quiet', { cwd, stdio: 'pipe' });
} catch {
staged = true;
}

try {
execSync('git diff --quiet', { cwd, stdio: 'pipe' });
} catch {
unstaged = true;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Differentiate “has changes” from git command failures.

Line 66 and Line 72 treat any git error as “changes exist”. That masks real failures (e.g., invalid repo/permission errors) and can send the flow down the wrong path.

Suggested fix
 export function gitHasChanges(cwd: string): { staged: boolean; unstaged: boolean } {
-  let staged = false;
-  let unstaged = false;
-
-  try {
-    execSync('git diff --cached --quiet', { cwd, stdio: 'pipe' });
-  } catch {
-    staged = true;
-  }
-
-  try {
-    execSync('git diff --quiet', { cwd, stdio: 'pipe' });
-  } catch {
-    unstaged = true;
-  }
+  const stagedRes = spawnSync('git', ['diff', '--cached', '--quiet'], {
+    cwd,
+    stdio: 'pipe',
+    shell: false,
+  });
+  if (stagedRes.status === null || stagedRes.status > 1) {
+    throw new Error((stagedRes.stderr || stagedRes.stdout || 'git diff --cached failed').toString().trim());
+  }
+
+  const unstagedRes = spawnSync('git', ['diff', '--quiet'], {
+    cwd,
+    stdio: 'pipe',
+    shell: false,
+  });
+  if (unstagedRes.status === null || unstagedRes.status > 1) {
+    throw new Error((unstagedRes.stderr || unstagedRes.stdout || 'git diff failed').toString().trim());
+  }
 
-  return { staged, unstaged };
+  return {
+    staged: stagedRes.status === 1,
+    unstaged: unstagedRes.status === 1,
+  };
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/batch.ts` around lines 64 - 74, The current try/catch around
execSync('git diff --cached --quiet', ...) and execSync('git diff --quiet', ...)
treats any git error as “changes exist”, which hides real failures; update the
catch blocks in src/commands/batch.ts to inspect the thrown error from execSync
(the Error object with a .status/exit code) and only set staged = true /
unstaged = true when the exit code indicates diffs were found (exit code 1); for
any other error (e.g., non-zero status other than 1 or missing repo/permission
errors) rethrow or propagate the original error so failures aren’t masked.
Ensure you reference the specific execSync calls and the staged/unstaged
variables when making the change.

Comment thread src/commands/batch.ts
Comment on lines +163 to +166
if (!existsSync(dir) || !statSync(dir).isDirectory()) {
outro(pc.red(`Directory not found: ${dir}`));
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle statSync errors when validating the target directory.

Line 163 can throw (e.g., permission-denied) even when existsSync(dir) is true, which will crash the command before your user-facing outro path runs.

Suggested fix
-  if (!existsSync(dir) || !statSync(dir).isDirectory()) {
+  let isDirectory = false;
+  try {
+    isDirectory = existsSync(dir) && statSync(dir).isDirectory();
+  } catch {
+    isDirectory = false;
+  }
+  if (!isDirectory) {
     outro(pc.red(`Directory not found: ${dir}`));
     return;
   }
📝 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
if (!existsSync(dir) || !statSync(dir).isDirectory()) {
outro(pc.red(`Directory not found: ${dir}`));
return;
}
let isDirectory = false;
try {
isDirectory = existsSync(dir) && statSync(dir).isDirectory();
} catch {
isDirectory = false;
}
if (!isDirectory) {
outro(pc.red(`Directory not found: ${dir}`));
return;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/batch.ts` around lines 163 - 166, The current directory check
uses existsSync(dir) then statSync(dir).isDirectory(), but statSync can throw
(e.g., permission denied) even when existsSync returns true; wrap the statSync
call in a try/catch (around the block that calls statSync and checks
isDirectory) so any thrown error is caught, call outro(pc.red(`Directory not
found: ${dir}`)) or a clearer message including err.message, and return;
specifically update the validation around existsSync and statSync in the batch
command to handle exceptions from statSync and avoid crashing.

Comment thread src/commands/batch.ts
Comment on lines +295 to +324
try {
const commitResult = gitCommit(repoPath, first.message, first.body);
await appendEntry({
timestamp: new Date().toISOString(),
message: first.body
? `${first.message}\n\n${first.body}`
: first.message,
diff,
model: config.model,
provider: config.provider,
});
console.log(
` ${pc.green(`✓ ${pc.bold(commitResult.hash)} ${commitResult.summary}`)}`,
);
results.push({
repo: repoPath,
repoName,
status: 'success',
message: first.message,
});
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
console.log(` ${pc.red(`✖ Commit failed: ${msg}`)}`);
results.push({
repo: repoPath,
repoName,
status: 'failed',
message: msg,
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

A history write failure is misreported as a commit failure.

In both commit paths, gitCommit(...) and appendEntry(...) share one try/catch. If commit succeeds but history append fails, the code logs “Commit failed” and records failed status even though the commit already exists.

Suggested fix pattern
-      try {
-        const commitResult = gitCommit(repoPath, first.message, first.body);
-        await appendEntry({
+      try {
+        const commitResult = gitCommit(repoPath, first.message, first.body);
+        let historyError: string | undefined;
+        try {
+          await appendEntry({
             timestamp: new Date().toISOString(),
             message: first.body
               ? `${first.message}\n\n${first.body}`
               : first.message,
             diff,
             model: config.model,
             provider: config.provider,
-        });
+          });
+        } catch (err) {
+          historyError = err instanceof Error ? err.message : String(err);
+        }
         console.log(
           `    ${pc.green(`✓ ${pc.bold(commitResult.hash)} ${commitResult.summary}`)}`,
         );
+        if (historyError) {
+          console.log(`    ${pc.yellow(`⚠ Committed, but failed to write history: ${historyError}`)}`);
+        }
         results.push({
           repo: repoPath,
           repoName,
           status: 'success',
           message: first.message,
         });
       } catch (err) {
         const msg = err instanceof Error ? err.message : String(err);
         console.log(`    ${pc.red(`✖ Commit failed: ${msg}`)}`);
         results.push({

Apply the same split in the interactive commit block (Line 407-440).

Also applies to: 407-440

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/batch.ts` around lines 295 - 324, The current try/catch around
gitCommit(...) and appendEntry(...) hides appendEntry failures as "Commit
failed"; split the operations so gitCommit(repoPath, first.message, first.body)
is executed and handled first (log success and push a success result using
commitResult, repoPath, repoName), then call appendEntry(...) in its own
try/catch; if appendEntry fails, log and record that the history append failed
(not the commit), and do not mark the commit itself as failed. Apply the same
split logic in the interactive commit block where gitCommit and appendEntry are
currently wrapped together.

Comment thread src/providers/index.ts
Comment on lines +62 to +70
export async function* completeStream(
configProvider: string,
baseUrlOverride: string | undefined,
params: Omit<ChatParams, 'baseUrl'>,
provider?: Provider,
): AsyncIterable<ProviderStreamChunk> {
const resolvedProvider = provider ?? getStreamingProvider(configProvider);
const baseUrl = getBaseUrl(configProvider, baseUrlOverride);
yield* resolvedProvider.completeStream!({ ...params, baseUrl });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate injected providers before calling completeStream.

At Line 68-70, the optional provider parameter can bypass getStreamingProvider(), but resolvedProvider.completeStream! is still invoked unconditionally. If a non-streaming provider is passed, this throws a raw runtime error instead of the intended CLI message.

Suggested fix
 export async function* completeStream(
   configProvider: string,
   baseUrlOverride: string | undefined,
   params: Omit<ChatParams, 'baseUrl'>,
   provider?: Provider,
 ): AsyncIterable<ProviderStreamChunk> {
   const resolvedProvider = provider ?? getStreamingProvider(configProvider);
+  if (!resolvedProvider.completeStream) {
+    throw new Error(
+      `Streaming is not supported for the '${configProvider}' provider. Use non-streaming mode.`,
+    );
+  }
   const baseUrl = getBaseUrl(configProvider, baseUrlOverride);
-  yield* resolvedProvider.completeStream!({ ...params, baseUrl });
+  yield* resolvedProvider.completeStream({ ...params, baseUrl });
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/providers/index.ts` around lines 62 - 70, In completeStream, validate
that the resolved provider actually supports streaming before invoking
resolvedProvider.completeStream; check whether resolvedProvider.completeStream
is a function and if not, either replace the injected provider with
getStreamingProvider(configProvider) or throw/return a clear CLI-friendly error.
Update the logic in completeStream to test the optional provider (and the result
of getStreamingProvider) for a completeStream method and only yield* from
resolvedProvider.completeStream when present, otherwise surface a descriptive
error referring to completeStream and getStreamingProvider so callers know a
non-streaming Provider was supplied.

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.

Improve invalid config JSON error message with file path

3 participants