Skip to content

fix: three dispatch-reliability bugs found dogfooding autonomous work - #413

Merged
getappz merged 5 commits into
masterfrom
fix-assignee-agent-instance-suffix
Aug 9, 2026
Merged

fix: three dispatch-reliability bugs found dogfooding autonomous work#413
getappz merged 5 commits into
masterfrom
fix-assignee-agent-instance-suffix

Conversation

@getappz

@getappz getappz commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Summary

Three real bugs found and fixed while dogfooding agentflare's own autonomous work dispatch (item #43) against a fresh design/plan cycle.

  • assignee_agent instance-suffix not recognized on redispatchitem::claim deliberately stores the raw claim owner (<agent>:<instance>) into assignee_agent on acquire. resolve_confirmed_agent (supervisor's discovery tick) and resolve_agent's assignee fallback (agentflare work's auto-routing) matched that field with an exact string comparison, so once an item had been claimed even once, its own assignee could never be recognized again on a later dispatch attempt — it would silently get skipped. Both now reuse item::agent_part (already used internally by claim()'s own handoff-freeze comparison) to strip the instance suffix before matching.

  • item done marked an item completed with zero work delivereditem_done unconditionally called mark_completed whenever the claim owner called done and no PR resulted, regardless of whether the claimed branch had any real commits. A headless run that only replied with text (no tool use, no commits) exited 0 and sailed straight through to "completed." Reuses branch_diverged (extracted from push_branch's existing squash-merge guard) to tell a genuinely empty run apart from one whose push/PR failed for another reason — a genuinely empty run now releases the claim and cleans up the (by-definition clean) worktree so the item is available for retry, but is left unchanged rather than falsely marked completed.

  • handoff(item_id=X) required a separate add_label call to actually queue dispatch — only a brand-new handed-off item got auto-labeled ready-for-work; an explicit item_id handoff onto an existing item only ever set the assignee. Extends auto-queuing to that path too, but only when it's safe (never claimed, still in a fresh state group) — an already-claimed/in-progress/done item is left alone, same protection the reply/continuation path already had.

Test plan

  • cargo build --workspace clean
  • cargo test --bin agentflare -- --skip fallback_scan — 1150 passed, 0 failed (fallback_scan_finds_matches_and_skips_target_dir is a pre-existing, environment-specific failure confirmed to fail identically on unmodified master)
  • New/updated tests per fix: resolve_confirmed_agent_recognizes_an_instance_suffixed_assignee, resolve_agent_falls_back_to_an_instance_suffixed_assignee, item_done_without_new_commits_leaves_the_item_unchanged (renamed + updated from a test that pinned the old buggy behavior), a_reply_to_an_already_claimed_item_is_not_relabeled_ready_for_work, an_explicit_item_id_handoff_labels_a_still_fresh_unclaimed_item
  • Verified live: rebuilt release binary, installed to ~/.cargo/bin/agentflare, restarted the daemon, confirmed the assignee_agent fix works against a real previously-claimed item in the running system

Summary by CodeRabbit

  • New Features

    • Improved assignment routing for items assigned to agent instances.
    • Added branch divergence checks to identify meaningful work before completing or merging items.
    • Handoffs now automatically label fresh, unclaimed backlog items as ready for work.
  • Bug Fixes

    • Previously claimed items are no longer incorrectly re-queued during handoffs.
    • Items with no changes remain unchanged instead of being marked complete.
    • Agent resolution now works reliably with instance-suffixed assignments.

shiva added 3 commits August 9, 2026 14:41
item::claim() deliberately stores the raw claim owner (<agent>:<instance>)
into assignee_agent on acquire, not just the bare agent name — intentional,
pinned by existing tests. But resolve_confirmed_agent (supervisor.rs's
discovery tick) and resolve_agent's assignee fallback (cli/work.rs) matched
that field against the agent registry with an exact string comparison, so
once an item had been claimed at least once, its own assignee could never
be recognized again on a later dispatch attempt — it would silently get
skipped or fall through to other routing rules.

Both now reuse item::agent_part (already used internally by claim()'s own
handoff-freeze comparison) to strip the instance suffix before matching,
instead of duplicating that logic or changing the write side.
item_done unconditionally called mark_completed whenever a claim owner
called done and no PR resulted, regardless of whether the claimed branch
had any real commits on it. A headless work run that only replies with
text (no tool use, no commits) previously exited 0 and sailed straight
through to being marked "completed" with zero code changed.

Reuses branch_diverged (extracted from push_branch's existing squash-merge
guard, now shared between flare-git-core and the main binary) to tell a
genuinely empty run apart from one whose push/PR failed for another
reason. A genuinely empty run now releases the claim and cleans up the
(by-definition clean) worktree so the item is available for retry, but
is left unchanged rather than falsely marked completed.

Agentflare-Agent: claude-code_2-1-226_agent
Agentflare-Branch: task/48
Agentflare-Item: 48
Previously handoff(item_id=X) only ever set assignee_agent — queuing an
existing item for the supervisor's discovery tick required a separate
item(action=add_label) call every time, unlike a brand-new handed-off item
which already got labeled ready-for-work automatically.

Extends that to the explicit item_id path, but only when it's actually
safe: the item has never been claimed and is still in a genuinely fresh
state group (backlog/unstarted/triage). An item that's already claimed,
in progress, in review, or completed is left alone, same as the existing
reply/continuation path already protects against — re-queuing live work
would be wrong regardless of which code path reached it.
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR canonicalizes instance-suffixed agent assignments, adds shared branch-divergence checks, changes no-commit completion handling, and refines ready-label behavior for explicit handoffs.

Changes

Agentflare workflow behavior

Layer / File(s) Summary
Canonical agent resolution
crates/agentflare-backend/src/item.rs, src/cli/work.rs, src/supervisor.rs
agent_part is public. CLI and supervisor resolution now remove instance suffixes before agent lookup.
Branch divergence and completion handling
crates/flare-git-core/src/worktree.rs, src/worktree.rs, src/mcp_server/item.rs, src/mcp_server/tests/action_tests.rs
Shared branch-divergence helpers determine whether work produced commits. No-commit completion releases claims and cleans the worktree while leaving the item unchanged.
Handoff queue labeling
src/mcp_server/handoff.rs
Explicit handoffs apply the existing ready-for-work label only to never-claimed items in backlog-like states. Tests cover claimed and fresh items.

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

Sequence Diagram(s)

sequenceDiagram
  participant Agent
  participant item_done
  participant Worktree
  participant flare_git_core
  participant ClaimLedger
  Agent->>item_done: submit completion
  item_done->>Worktree: check task branch divergence
  Worktree->>flare_git_core: compare task branch with target
  flare_git_core-->>Worktree: return divergence result
  item_done->>ClaimLedger: release claim
  item_done->>Worktree: clean up worktree
  item_done-->>Agent: return completed or unchanged status
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the pull request's primary purpose: fixing three dispatch-reliability bugs found during autonomous work testing.
Description check ✅ Passed The description explains all three fixes and rationale, documents test results, and provides regression coverage, although it omits the template's reviewer-notes section.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-assignee-agent-instance-suffix

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

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

🤖 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 454-466: Separate commit existence from content divergence: add a
fallible helper in crates/flare-git-core/src/worktree.rs near branch_diverged
that reports whether target_branch..branch contains commits, expose it through
src/worktree.rs, and update item_done in src/mcp_server/item.rs so only a
successful zero-commit check marks the item unchanged; preserve the claim and
worktree on Git-check errors while allowing committed-but-already-applied
branches to complete. Add tests in src/mcp_server/tests/action_tests.rs covering
completion with committed content already on target and claim release for the
true zero-commit case.

In `@src/mcp_server/handoff.rs`:
- Around line 110-115: In the handoff eligibility check around current_owner,
use the fallible durable claim-history lookup to set never_claimed, and treat
lookup errors as not eligible so the item is not requeued. In
src/mcp_server/handoff.rs lines 110-115, replace the active-owner check
accordingly; in lines 490-515, release or expire the seeded claim, move the item
to an eligible state, and assert that explicit handoff does not add
ready-for-work.
- Line 115: Update the ready-label handling in handoff to propagate the Result
from agentflare_backend::item::add_label instead of discarding it with let _.
Ensure any attachment failure is returned through handoff so the operation does
not report success when the item was not queued.
🪄 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: 34a01646-9927-4df1-a4bb-3eaf6cc4839b

📥 Commits

Reviewing files that changed from the base of the PR and between 3b593ab and 2447bfd.

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

Comment on lines +454 to +466
pub fn branch_diverged(repo_root: &Path, branch: &str, target_branch: &str) -> bool {
match run_git_in(
repo_root,
&["rev-list", "--count", &format!("{target_branch}..{branch}")],
) {
Ok(count) if count != "0" => {}
_ => return false,
}
!run_git_in_ok(
repo_root,
&["diff", "--quiet", &format!("{target_branch}..{branch}")],
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Separate commit existence from push/PR divergence.

branch_diverged returns false when the branch has zero commits, when target already has identical content after a squash merge, and when a Git query fails. item_done treats all three cases as an empty run. It then cleans the worktree, releases the claim, and leaves the item unchanged. A branch with committed work that is already present on target cannot complete through this path.

  • crates/flare-git-core/src/worktree.rs#L454-L466: add a fallible helper that reports whether target_branch..branch has commits. Keep branch_diverged for push and PR suppression.
  • src/worktree.rs#L25-L34: expose the fallible commit-existence helper.
  • src/mcp_server/item.rs#L645-L684: classify an item as unchanged only when the commit check succeeds and reports zero commits. Preserve the claim and worktree when the Git check fails.
  • src/mcp_server/tests/action_tests.rs#L357-L405: add coverage for a task branch with committed content already applied to target. Assert completion. Also assert that the true zero-commit case releases the claim for a retry.
📍 Affects 4 files
  • crates/flare-git-core/src/worktree.rs#L454-L466 (this comment)
  • src/worktree.rs#L25-L34
  • src/mcp_server/item.rs#L645-L684
  • src/mcp_server/tests/action_tests.rs#L357-L405
🤖 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 454 - 466, Separate
commit existence from content divergence: add a fallible helper in
crates/flare-git-core/src/worktree.rs near branch_diverged that reports whether
target_branch..branch contains commits, expose it through src/worktree.rs, and
update item_done in src/mcp_server/item.rs so only a successful zero-commit
check marks the item unchanged; preserve the claim and worktree on Git-check
errors while allowing committed-but-already-applied branches to complete. Add
tests in src/mcp_server/tests/action_tests.rs covering completion with committed
content already on target and claim release for the true zero-commit case.

Comment thread src/mcp_server/handoff.rs Outdated
Comment on lines +110 to +115
let never_claimed = agentflare_backend::claim::current_owner(conn, id).is_none();
if never_claimed
&& matches!(state.group_name.as_str(), "backlog" | "unstarted" | "triage")
&& let Some(ready_id) = ready_label_id(conn, &project.id)
{
let _ = agentflare_backend::item::add_label(conn, id, &ready_id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use durable claim history for the never_claimed gate.

current_owner only returns an active claim. A released or expired prior claim returns None. If that item later returns to backlog, unstarted, or triage, this code adds ready-for-work despite the requirement that only never-claimed items are requeued.

  • src/mcp_server/handoff.rs#L110-L115: replace current_owner with a fallible historical-claim check. Do not requeue when claim history cannot be read.
  • src/mcp_server/handoff.rs#L490-L515: release or expire the seeded claim, return the item to an eligible state, then assert that the explicit handoff does not add ready-for-work.
📍 Affects 1 file
  • src/mcp_server/handoff.rs#L110-L115 (this comment)
  • src/mcp_server/handoff.rs#L490-L515
🤖 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/mcp_server/handoff.rs` around lines 110 - 115, In the handoff eligibility
check around current_owner, use the fallible durable claim-history lookup to set
never_claimed, and treat lookup errors as not eligible so the item is not
requeued. In src/mcp_server/handoff.rs lines 110-115, replace the active-owner
check accordingly; in lines 490-515, release or expire the seeded claim, move
the item to an eligible state, and assert that explicit handoff does not add
ready-for-work.

Comment thread src/mcp_server/handoff.rs
&& matches!(state.group_name.as_str(), "backlog" | "unstarted" | "triage")
&& let Some(ready_id) = ready_label_id(conn, &project.id)
{
let _ = agentflare_backend::item::add_label(conn, id, &ready_id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Propagate the ready-label attachment error.

When the ready label exists, add_label can still fail, for example after concurrent label deletion or during a database error. Line 115 discards that error, so handoff reports success but does not queue the item.

Proposed fix
-                        let _ = agentflare_backend::item::add_label(conn, id, &ready_id);
+                        agentflare_backend::item::add_label(conn, id, &ready_id)
+                            .map_err(map_backend_err)?;
📝 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
let _ = agentflare_backend::item::add_label(conn, id, &ready_id);
agentflare_backend::item::add_label(conn, id, &ready_id)
.map_err(map_backend_err)?;
🤖 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/mcp_server/handoff.rs` at line 115, Update the ready-label handling in
handoff to propagate the Result from agentflare_backend::item::add_label instead
of discarding it with let _. Ensure any attachment failure is returned through
handoff so the operation does not report success when the item was not queued.

@getappz
getappz merged commit 79a2072 into master Aug 9, 2026
16 checks passed
@getappz
getappz deleted the fix-assignee-agent-instance-suffix branch August 9, 2026 13:25
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