get2knowio / get2knowio/maverick

fly: a deterministically-failing bead is reselected forever without --max-beads

Open
#189 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Python
Stars
4
Forks
0
Avg merge
17h 37m
Merged PRs (30d)
7

Description

## Description

`maverick fly` (both isolated and non-isolated modes) has no mechanism to exclude a bead that fails *deterministically* (not a transient error) from re-selection. Only a **succeeded** bead is ever added to `completed_bead_ids`; a failed bead's `bd` state is never changed (no close, no defer, no label). Since `select_next_bead`'s dedup check only skips beads already in `completed_bead_ids`, and `bd ready` still returns the failed bead on every subsequent poll, the drain loop reselects and reprocesses the same failing bead forever within one `maverick fly` invocation — unless the run happens to be bounded externally by `--max-beads`.

`--max-beads` defaults to `0` ("unlimited — drains the queue"), which is the common/default invocation shape, so this affects normal usage: any bead with a persistent, non-transient bug (e.g. a gate check that can never pass, an agent that can't produce a valid implementation) turns a `maverick fly` run into a hard, non-terminating loop instead of a bounded number of attempts followed by moving on to the next ready bead or ending the run.

## Reproduction

```bash
# A bead whose implementation deterministically fails the gate (e.g. a lint
# rule it can never satisfy) never gets skipped or excluded:
maverick fly --epic # no --max-beads
```

Expected: the failing bead is abandoned (as it already correctly is, after exhausting its fix-round budget) and the loop moves on to the next ready bead, or ends once nothing else is ready.

Actual: the loop reselects the same failing bead every iteration — `processed_count`/`failed_count` climb, but the run never progresses past it and never terminates on its own.

Confirmed empirically while writing `tests/integration/fly/test_isolated_no_premature_commit.py` (057-isolated-bead-workspaces): the first draft of that test (without `max_beads=1`) hung past the integration-test timeout on 3 of 4 failure scenarios; adding `max_beads=1` was required to observe a single bounded attempt. Reproduces identically in non-isolated mode — the gap is in shared code, not isolation-specific.

## Root Cause

- `record_outcome` (`src/maverick/workflows/fly_beads/actions.py:1724`) only appends to `completed_bead_ids` when `succeeded` is true:
```python
completed = list(state["completed_bead_ids"])
if succeeded and bead_id not in completed:
completed.append(bead_id)
```
A failed bead's id is never recorded anywhere that later selection consults.
- `select_next_bead` (`src/maverick/workflows/fly_beads/actions.py:194`) resolves the next bead purely via `bd_select(epic_id=..., cwd=...)` (a live `bd ready` query) and only skips a bead already in `completed_bead_ids`:
```python
bead_id = bead_dict["bead_id"]
completed: list[str] = list(state["completed_bead_ids"])
if bead_id in completed:
# Already done in a prior run (resumed from checkpoint) — skip.
...
```
There is no equivalent skip for a bead that failed *this run*.
- `abandon_bead` (`src/maverick/workflows/fly_beads/actions.py:1678`) marks the bead failed on Burr state only (`bead_aborted=True, bead_failed=True`) — it never touches `bd`'s own state (no `bd close`, no `bd defer`, no label), so the bead stays `bd ready` and reappears on the very next `bd_select` call.

## Suggested Fix

Track failed bead ids for the lifetime of the run (mirroring `completed_bead_ids`, e.g. a new `failed_bead_ids` state slot written by `record_outcome` and read by `select_next_bead`) and skip them the same way an already-completed bead is skipped — bounding each bead to exactly one attempt per invocation regardless of `--max-beads`. Whether a failed bead should also get a durable `bd`-level marker (defer/label) so it's excluded across *separate* `maverick fly` invocations too is a related but separate design question worth deciding alongside the fix.

## Files

- `src/maverick/workflows/fly_beads/actions.py` — `select_next_bead` (line 194), `abandon_bead` (line 1678), `record_outcome` (line 1724)

Contributor guide

Open the contributing guide

Research direction

Start in src/maverick/workflows/fly_beads/actions.py with select_next_bead, abandon_bead, and record_outcome, then read the related fly state handling. Run tests/integration/fly/test_isolated_no_premature_commit.py and inspect its failure scenarios. Done means a deterministically failing bead is not reselected during one unlimited fly run, in both isolated and non-isolated modes.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
cli, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.