Skip to content

fix(work): dispatched jobs leave real edits uncommitted, reported as success - #431

Merged
getappz merged 6 commits into
masterfrom
task/57
Aug 10, 2026
Merged

fix(work): dispatched jobs leave real edits uncommitted, reported as success#431
getappz merged 6 commits into
masterfrom
task/57

Conversation

@getappz

@getappz getappz commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Summary

Root cause confirmed: item_done's nothing_was_ever_committed check (based on branch divergence) couldn't distinguish "agent edited files but forgot git commit" from "agent legitimately made no code changes" — both produced a clean/no-divergence signal, so done reported success while real edits sat stranded in the worktree.

Fix, two parts:

  1. item_done now auto-commits. Added commit_uncommitted in crates/flare-git-core/src/worktree.rs (thin-wrapped in src/worktree.rs) — checks git status --porcelain in the item's worktree and, if dirty, runs git add -A && git commit -m <message> using the agent's own summary as the commit message. Wired into item_done (src/mcp_server/item.rs) right before the push/PR and no-op classification run, so a forgotten commit becomes a real commit → push → PR instead of a silent "unchanged" success. I put this at the item_done choke point rather than in execute_work (as the item suggested) since it's the single place both the daemon-dispatched work path and any interactive mcp__flare__item done call go through — one fix instead of two.
  2. Prompt now instructs committing. build_prompt (src/cli/work.rs) explicitly tells the agent to commit before finishing, while still permitting a legitimate zero-commit outcome when no code change is warranted.

The #41/#53 no-op path is untouched — a clean worktree short-circuits before any commit is attempted.

Tests: 3 new unit tests for commit_uncommitted in flare-git-core, a rewritten item_done test proving a dirty worktree now lands as a real commit and status: "completed" (plus a fallback test for when auto-commit itself can't run), and a build_prompt test for the new instruction. Full touched-module test runs (166 tests across item_done, cli::work, worktree, mcp_server::tests) all pass; cargo clippy/cargo fmt clean.

Summary by CodeRabbit

  • New Features

    • Automatically commits uncommitted worktree changes when completing an item.
    • Uses the request summary as the commit message, with a fallback when unavailable.
    • Agents are instructed to commit relevant changes before finishing, while allowing no commit when no code changes are needed.
  • Bug Fixes

    • Preserves worktree changes when automatic commits fail.
  • Tests

    • Added coverage for successful auto-commits, no-op scenarios, failed commits, and completion prompts.

…t agents to commit

A headless work run that edited real files but never ran git commit looked
identical, from the outside, to a genuine no-op: the branch never diverged
from target, so item_done's nothing_was_ever_committed check treated it the
same as a design-only run with zero code changes, reported success, and left
the real edits stranded uncommitted in the worktree (items #52, #54).

item_done now commits any uncommitted worktree changes itself, using the
agent's own summary as the commit message, before the push/PR and no-op
classification run -- so a forgotten commit becomes a real commit+push+PR
instead of a silent success with stranded edits. build_prompt's headless
instructions now also explicitly tell the agent to commit its own work
before finishing, while still allowing a legitimate zero-commit outcome
when no code change is warranted (items #41, #53).

Agentflare-Agent: claude-code
Agentflare-Branch: task/57
Agentflare-Item: 57
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds automatic commits for dirty item worktrees. It exposes the commit operation, updates agent prompts, invokes commits during item_done, and adds tests for successful, clean, missing, and failed worktrees.

Changes

Worktree auto-commit

Layer / File(s) Summary
Core worktree commit operation
crates/flare-git-core/src/worktree.rs
Adds commit_uncommitted to stage and commit dirty worktrees. Tests cover successful, clean, and nonexistent worktrees.
Completion wiring and agent instructions
src/worktree.rs, src/cli/work.rs
Exposes the core operation and requires agents to commit changes before finishing, while allowing zero commits when no code change is needed.
Item completion auto-commit flow
src/mcp_server/item.rs, src/mcp_server/tests/action_tests.rs
Commits pending changes before completion processing. Tests cover successful commits and preservation of worktrees when Git operations fail.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant Agent
  participant item_done
  participant src_worktree
  participant flare_git_core
  participant Git
  Agent->>item_done: Complete item with summary
  item_done->>src_worktree: commit_uncommitted(item, repo_root, message)
  src_worktree->>flare_git_core: Commit pending worktree changes
  flare_git_core->>Git: Status, stage, and commit
  Git-->>flare_git_core: Commit result
  flare_git_core-->>item_done: Success or failure
  item_done->>Git: Continue push or completion processing
``

</details>

<!-- walkthrough_end -->
<!-- pre_merge_checks_walkthrough_start -->

<details>
<summary>🚥 Pre-merge checks | ✅ 5</summary>

<details>
<summary>✅ Passed checks (5 passed)</summary>

|         Check name         | Status   | Explanation                                                                                                                                      |
| :------------------------: | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |
|         Title check        | ✅ Passed | The title clearly identifies the primary fix: dispatched work leaves edits uncommitted and reports success incorrectly.                          |
|      Description check     | ✅ Passed | The description explains the cause, fix, tests, and edge cases, but it omits the template's explicit Test plan and Notes for reviewers headings. |
|     Docstring Coverage     | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.                                       |
|     Linked Issues check    | ✅ Passed | Check skipped because no linked issues were found for this pull request.                                                                         |
| Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request.                                                                         |

</details>

</details>

<!-- pre_merge_checks_walkthrough_end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

<details>
<summary>📝 Generate docstrings</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> Create stacked PR
- [ ] <!-- {"checkboxId": "3e1879ae-f29b-4d0d-8e06-d12b7ba33d98"} --> Commit on current branch

</details>
<details>
<summary>🧪 Generate unit tests (beta)</summary>

- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Create PR with unit tests
- [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Commit unit tests in branch `task/57`

</details>

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---




<sub>Comment `@coderabbitai help` to get the list of available commands.</sub>

<!-- tips_end -->
Loading

@getappz getappz changed the title work: dispatched jobs leave real edits uncommitted, reported as success fix(work): dispatched jobs leave real edits uncommitted, reported as success Aug 10, 2026
shiva added 2 commits August 10, 2026 15:10
Agentflare-Agent: claude-code
Agentflare-Branch: task/57
Agentflare-Item: 57
Agentflare-Agent: claude-code
Agentflare-Branch: task/57
Agentflare-Item: 57
@getappz
getappz enabled auto-merge (squash) August 10, 2026 09:49

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@crates/flare-git-core/src/worktree.rs`:
- Around line 704-731: Update commit_uncommitted’s automatic git commit step to
use the existing timeout-aware helper instead of the indefinitely waiting
run_git_in call. Preserve the current boolean behavior by returning false when
the commit times out or otherwise fails, while leaving status and staging
behavior unchanged.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b139e48f-e9b5-4f9d-9d91-9643c0a8dc20

📥 Commits

Reviewing files that changed from the base of the PR and between cfee6b5 and 4a89cb4.

📒 Files selected for processing (5)
  • crates/flare-git-core/src/worktree.rs
  • src/cli/work.rs
  • src/mcp_server/item.rs
  • src/mcp_server/tests/action_tests.rs
  • src/worktree.rs

Comment on lines +704 to +731
/// Commits any uncommitted changes sitting in `item`'s worktree checkout.
///
/// An agent can make real file edits and still exit without ever running
/// `git commit` itself -- with no commit, the branch never diverges from
/// its target, so `item_done` (main binary) can't tell that apart from a
/// genuine no-op and the edits are silently stranded while `done` reports
/// success (item #57). Called before that divergence check runs, so a
/// forgotten commit gets made here first instead of falling through to the
/// "nothing was committed" path.
///
/// No-ops (returns `false`) when the worktree doesn't exist, is already
/// clean, or `add`/`commit` fails for some other reason -- the caller's
/// existing dirty-tree handling (`cleanup_item_worktree` refusing to
/// remove it) still covers all of those exactly as it did before.
pub fn commit_uncommitted(item: &Item, repo_root: &Path, message: &str) -> bool {
let worktree_path = repo_root
.join(".worktrees")
.join("task")
.join(item.sequence_id.to_string());
match run_git_in(&worktree_path, &["status", "--porcelain"]) {
Ok(out) if !out.trim().is_empty() => {}
_ => return false,
}
if run_git_in(&worktree_path, &["add", "-A"]).is_err() {
return false;
}
run_git_in(&worktree_path, &["commit", "-m", message]).is_ok()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add a timeout for the automatic commit.

Line 730 calls run_git_in, which waits indefinitely for git commit. A commit hook or signing program can block this MCP completion request indefinitely. Use the existing timeout helper and return false on timeout.

Proposed fix
-    run_git_in(&worktree_path, &["commit", "-m", message]).is_ok()
+    matches!(
+        run_output_timeout(
+            crate::shell::git_binary(),
+            &["commit", "-m", message],
+            &worktree_path,
+            30,
+        ),
+        Ok(out) if out.status.success()
+    )
📝 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
/// Commits any uncommitted changes sitting in `item`'s worktree checkout.
///
/// An agent can make real file edits and still exit without ever running
/// `git commit` itself -- with no commit, the branch never diverges from
/// its target, so `item_done` (main binary) can't tell that apart from a
/// genuine no-op and the edits are silently stranded while `done` reports
/// success (item #57). Called before that divergence check runs, so a
/// forgotten commit gets made here first instead of falling through to the
/// "nothing was committed" path.
///
/// No-ops (returns `false`) when the worktree doesn't exist, is already
/// clean, or `add`/`commit` fails for some other reason -- the caller's
/// existing dirty-tree handling (`cleanup_item_worktree` refusing to
/// remove it) still covers all of those exactly as it did before.
pub fn commit_uncommitted(item: &Item, repo_root: &Path, message: &str) -> bool {
let worktree_path = repo_root
.join(".worktrees")
.join("task")
.join(item.sequence_id.to_string());
match run_git_in(&worktree_path, &["status", "--porcelain"]) {
Ok(out) if !out.trim().is_empty() => {}
_ => return false,
}
if run_git_in(&worktree_path, &["add", "-A"]).is_err() {
return false;
}
run_git_in(&worktree_path, &["commit", "-m", message]).is_ok()
}
/// Commits any uncommitted changes sitting in `item`'s worktree checkout.
///
/// An agent can make real file edits and still exit without ever running
/// `git commit` itself -- with no commit, the branch never diverges from
/// its target, so `item_done` (main binary) can't tell that apart from
/// a genuine no-op and the edits are silently stranded while `done` reports
/// success (item `#57`). Called before that divergence check runs, so a
/// forgotten commit gets made here first instead of falling through to the
/// "nothing was committed" path.
///
/// No-ops (returns `false`) when the worktree doesn't exist, is already
/// clean, or `add`/`commit` fails for some other reason -- the caller's
/// existing dirty-tree handling (`cleanup_item_worktree` refusing to
/// remove it) still covers all of those exactly as it did before.
pub fn commit_uncommitted(item: &Item, repo_root: &Path, message: &str) -> bool {
let worktree_path = repo_root
.join(".worktrees")
.join("task")
.join(item.sequence_id.to_string());
match run_git_in(&worktree_path, &["status", "--porcelain"]) {
Ok(out) if !out.trim().is_empty() => {}
_ => return false,
}
if run_git_in(&worktree_path, &["add", "-A"]).is_err() {
return false;
}
matches!(
run_output_timeout(
crate::shell::git_binary(),
&["commit", "-m", message],
&worktree_path,
30,
),
Ok(out) if out.status.success()
)
}
🤖 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 `@crates/flare-git-core/src/worktree.rs` around lines 704 - 731, Update
commit_uncommitted’s automatic git commit step to use the existing timeout-aware
helper instead of the indefinitely waiting run_git_in call. Preserve the current
boolean behavior by returning false when the commit times out or otherwise
fails, while leaving status and staging behavior unchanged.

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.

1 participant