Skip to content

fix: check staged changes before committing to prevent 'nothing to commit' error#2991

Merged
wwwillchen merged 6 commits intodyad-sh:mainfrom
wwwillchen-bot:claude/debug-session-2805-00MLS
Mar 11, 2026
Merged

fix: check staged changes before committing to prevent 'nothing to commit' error#2991
wwwillchen merged 6 commits intodyad-sh:mainfrom
wwwillchen-bot:claude/debug-session-2805-00MLS

Conversation

@wwwillchen-bot
Copy link
Copy Markdown
Collaborator

@wwwillchen-bot wwwillchen-bot commented Mar 11, 2026

Summary

  • Fixes a bug where the response processor would attempt git commit even when there were no staged changes, causing "nothing to commit" errors
  • Adds a hasStagedChanges utility function to git_utils.ts that checks whether there are actually staged files before committing
  • Updates the commit flow in response_processor.ts to check for staged changes first, skipping the commit when unnecessary

Test plan

  • All 897 existing tests pass
  • Lint, formatting, and type checks pass
  • Verify that file write/rename/delete operations still commit correctly when changes are staged
  • Verify that no error is thrown when git operations result in no staged changes

πŸ€– Generated with Claude Code


Open with Devin

claude and others added 5 commits March 11, 2026 11:40
…ng to commit" error

When the AI regenerates a file with identical content, fs.writeFileSync succeeds
but git has no actual diff to commit. The response_processor previously determined
hasChanges based on whether files were processed (written/renamed/deleted) rather
than whether git actually had staged changes, causing "Failed to create commit.
nothing to commit, working tree clean" errors.

Now checks isGitStatusClean() after staging files but before calling gitCommit().
If the working tree is clean (no actual diff), we skip the commit gracefully
instead of throwing an error.

Fixes dyad-sh#2805

https://claude.ai/code/session_015X6u6UE1z6GLUr2MvLL1yk
…tting

Replace isGitStatusClean (git status --porcelain) with new hasStagedChanges
(git diff --cached --quiet) to correctly detect whether there are staged
changes to commit. The previous approach would fail when unrelated
unstaged/untracked files existed in the working tree.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
The test mock was missing the new hasStagedChanges function, causing
vitest to fail with 'No export defined on mock' error.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
git diff --cached --quiet can return exit codes >1 for errors (e.g., not
inside a repo, I/O failure). Previously these were conflated with exit
code 1 (has staged changes), silently masking errors. Now explicitly
throw for unexpected exit codes, matching the pattern used by other
execGit calls in this file.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The import was flagged by oxlint as unused, causing CI build failure.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@wwwillchen
Copy link
Copy Markdown
Collaborator

@BugBot run

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the robustness of the application's Git integration by preventing unnecessary commit attempts. It introduces a pre-commit check to ensure that git commit is only invoked when there are actual staged changes, thereby eliminating 'nothing to commit' errors and improving the reliability of automated Git operations within the system.

Highlights

  • Bug Fix: Resolved an issue where the system would attempt to execute git commit even when no changes were staged, leading to 'nothing to commit' errors.
  • New Utility Function: Introduced a new utility function, hasStagedChanges, in git_utils.ts to reliably check for the presence of staged modifications.
  • Commit Flow Enhancement: Modified the commit process in response_processor.ts to first verify if there are actual staged changes using the new utility, skipping the commit operation if no changes are detected.
Changelog
  • src/tests/chat_stream_handlers.test.ts
    • Mocked the new hasStagedChanges function to simulate its behavior in tests.
  • src/ipc/processors/response_processor.ts
    • Imported the hasStagedChanges utility.
    • Wrapped the commit logic within a conditional block that checks for staged changes before proceeding with the commit.
  • src/ipc/utils/git_utils.ts
    • Added the hasStagedChanges asynchronous function, which determines if there are staged changes using either native Git commands (git diff --cached --quiet) or the isomorphic Git library's status matrix.
Activity
  • The pull request was authored by wwwillchen-bot and generated using Claude Code, indicating an automated creation process.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with πŸ‘ and πŸ‘Ž on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request correctly addresses a bug where git commit could be called without any staged changes, leading to an error. The introduction of the hasStagedChanges utility is a clean solution, and the implementation for both native git and isomorphic-git appears correct. My review includes a couple of suggestions to improve maintainability and test coverage for the new logic, with one suggestion emphasizing readability as per repository guidelines.

gitSetRemoteUrl: vi.fn(),
gitStatus: vi.fn().mockResolvedValue([]),
getGitUncommittedFiles: vi.fn().mockResolvedValue([]),
hasStagedChanges: vi.fn().mockResolvedValue(true),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

While this mock is necessary, the pull request would be strengthened by adding a new test case to verify the new behavior. Specifically, a test that mocks hasStagedChanges to return false and asserts that gitCommit is not called, ensuring the commit-skipping logic is working as intended.

Comment on lines +278 to +281
const statusMatrix = await git.statusMatrix({ fs, dir: path });
// row[1] = HEAD status, row[3] = stage status
// If stage differs from HEAD, there are staged changes
return statusMatrix.some((row) => row[3] !== row[1]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The logic statusMatrix.some((row) => row[3] !== row[1]) is correct but a bit cryptic. For better maintainability, consider adding a more detailed comment explaining why this comparison works for detecting staged changes with isomorphic-git's status matrix. This would help future developers understand the different cases (new, modified, deleted files) and the meaning of the numeric status codes.

    const statusMatrix = await git.statusMatrix({ fs, dir: path });
    // row[1] = HEAD status, row[3] = stage status.
    // If stage differs from HEAD, there are staged changes. This works because of the
    // status codes used by isomorphic-git's statusMatrix:
    // - head: 0=absent, 1=present
    // - stage: 0=absent, 1=identical to HEAD, 2=added, 3=modified
    // This correctly covers new (0 vs 2), modified (1 vs 3), and deleted (1 vs 0)
    // staged files, while ignoring unstaged changes.
    return statusMatrix.some((row) => row[3] !== row[1]);
References
  1. This comment aligns with the principle of favoring readability over conciseness, as detailed in the rule about explicit if/else blocks, by suggesting adding comments to clarify cryptic logic for better maintainability.

@wwwillchen
Copy link
Copy Markdown
Collaborator

@BugBot run

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

βœ… Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 11, 2026

Greptile Summary

This PR fixes a bug where processFullResponseActions would call git commit even when no staged changes existed β€” occurring when the AI rewrites files with content identical to what was already committed, causing git to throw "nothing to commit" errors. It adds a hasStagedChanges utility to git_utils.ts and wraps the entire commit/amend/db-update block behind a staged-change guard in response_processor.ts. Documentation for the Claude skill and git-workflow rules is also updated with bot-account push and label-permission guidance.

Key changes:

  • hasStagedChanges in git_utils.ts uses git diff --cached --quiet (exit code semantics: 0 = no changes, 1 = changes, anything else = error) for native git, and statusMatrix row comparison (row[3] !== row[1]) for isomorphic-git β€” both implementations are correct.
  • In response_processor.ts, the commit block is now skipped when hasStagedChanges returns false, and hasChanges is set to false, which propagates to the return value as updatedFiles: false. Callers should be verified to handle this case correctly.
  • The test mock for hasStagedChanges defaults to true, preserving existing test coverage, but no new test exercises the false path.

Confidence Score: 4/5

  • Safe to merge β€” the fix is correct and prevents a real error, with only minor gaps in test coverage and a semantic side-effect worth verifying.
  • The core hasStagedChanges logic is sound for both native git and isomorphic-git paths. The restructuring of the commit block is clean. The main concerns are: (1) no test for the staged = false branch, and (2) setting hasChanges = false changes updatedFiles semantics for callers. Neither is blocking, but both warrant follow-up.
  • src/ipc/processors/response_processor.ts β€” verify how callers interpret updatedFiles: false when files were written but produced no git diff.

Important Files Changed

Filename Overview
src/ipc/utils/git_utils.ts Adds hasStagedChanges utility that correctly uses git diff --cached --quiet (exit code 1 = staged changes) for native git and statusMatrix row comparison for isomorphic-git; both paths are logically sound.
src/ipc/processors/response_processor.ts Integrates hasStagedChanges guard before gitCommit to prevent "nothing to commit" errors; correctly restructures the commit/amend/db-update block, though setting hasChanges = false changes the updatedFiles return value semantics when staging produces no diff.
src/tests/chat_stream_handlers.test.ts Adds mock for hasStagedChanges defaulting to true so existing tests pass; no new test cases cover the false path (skipping commit).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[processFullResponseActions] --> B{hasChanges?\nwrittenFiles / renamedFiles /\ndeletedFiles / packages}
    B -- No --> Z[Return updatedFiles: false]
    B -- Yes --> C[gitAdd staged files\ngitRemove deleted files\ngitAdd renamed files]
    C --> D{hasStagedChanges?}
    D -- No staged diff\ne.g. AI wrote identical content --> E[Log: skipping commit\nhasChanges = false]
    E --> Z
    D -- Yes staged diff --> F[gitCommit initial commit]
    F --> G{getGitUncommittedFiles?\nfiles changed outside Dyad}
    G -- None --> H[Save commitHash to DB]
    G -- Found --> I[gitAddAll\ngitCommit --amend]
    I --> J{amend succeeded?}
    J -- Yes --> H
    J -- No --> K[Log error\nset extraFilesError]
    K --> H
    H --> L[Return updatedFiles: true]
Loading

Last reviewed commit: d343737

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 3 files

Confidence score: 3/5

  • There is a concrete regression risk in src/ipc/processors/response_processor.ts: deriving updatedFiles from staged Git state can misclassify ignored-file writes as unchanged, which can affect user-visible update handling.
  • Given the issue’s medium severity (6/10) and high confidence (9/10), this carries some real merge risk rather than a purely cosmetic concern.
  • Pay close attention to src/ipc/processors/response_processor.ts - updatedFiles change detection may miss ignored-file modifications.
Prompt for AI agents (unresolved issues)

Check if these issues are valid β€” if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/ipc/processors/response_processor.ts">

<violation number="1" location="src/ipc/processors/response_processor.ts:568">
P2: Don't derive `updatedFiles` from staged git state; ignored-file writes will now be treated as if nothing changed.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

// resulting in no actual git diff and causing "nothing to commit" errors.
// We check staged changes specifically (not the entire working tree) to avoid
// false positives from unrelated unstaged/untracked files.
const staged = await hasStagedChanges({ path: appPath });
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 11, 2026

Choose a reason for hiding this comment

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

P2: Don't derive updatedFiles from staged git state; ignored-file writes will now be treated as if nothing changed.

Prompt for AI agents
Check if this issue is valid β€” if so, understand the root cause and fix it. At src/ipc/processors/response_processor.ts, line 568:

<comment>Don't derive `updatedFiles` from staged git state; ignored-file writes will now be treated as if nothing changed.</comment>

<file context>
@@ -558,45 +559,59 @@ export async function processFullResponseActions(
+      // resulting in no actual git diff and causing "nothing to commit" errors.
+      // We check staged changes specifically (not the entire working tree) to avoid
+      // false positives from unrelated unstaged/untracked files.
+      const staged = await hasStagedChanges({ path: appPath });
+      if (!staged) {
+        logger.log(
</file context>
Fix with Cubic

@github-actions github-actions bot added the needs-human:review-issue ai agent flagged an issue that requires human review label Mar 11, 2026
@wwwillchen wwwillchen merged commit ea36c83 into dyad-sh:main Mar 11, 2026
8 of 11 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

🎭 Playwright Test Results

❌ Some tests failed

OS Passed Failed Flaky Skipped
🍎 macOS 391 0 2 123
πŸͺŸ Windows 394 3 5 123

Summary: 785 passed, 3 failed, 7 flaky, 246 skipped

Failed Tests

πŸͺŸ Windows

  • github.spec.ts > create and sync to new repo
    • TimeoutError: page.waitForSelector: Timeout 5000ms exceeded.
  • github.spec.ts > create and sync to existing repo
    • Error: expect(locator).toMatchAriaSnapshot(expected) failed
  • github.spec.ts > create and sync to existing repo - custom branch
    • Error: expect(locator).toMatchAriaSnapshot(expected) failed

⚠️ Flaky Tests

🍎 macOS

  • git_collaboration.spec.ts > Git Collaboration > should create, switch, rename, merge, and delete branches (passed after 1 retry)
  • setup_flow.spec.ts > Setup Flow > setup banner shows correct state when node.js is installed (passed after 1 retry)

πŸͺŸ Windows

  • chat_mode.spec.ts > chat mode selector - ask mode (passed after 1 retry)
  • edit_code.spec.ts > edit code (passed after 1 retry)
  • security_review.spec.ts > security review - multi-select and fix issues (passed after 1 retry)
  • setup_flow.spec.ts > Setup Flow > setup banner shows correct state when node.js is installed (passed after 1 retry)
  • template-create-nextjs.spec.ts > create next.js app (passed after 1 retry)

πŸ“Š View full report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-human:review-issue ai agent flagged an issue that requires human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants