Skip to content

Hot-loader ignores --spec filter and re-introduces completed specs #630

Description

@mickume

Bug Summary

When af plan --spec <name> is used to create a filtered plan, the hot-loader at sync barriers discovers and injects unrelated specs into the running graph — including specs that were completed long ago. This causes af code to create worktrees, dispatch sessions, and burn tokens on work that was never requested.

Reproduction

  1. Have multiple spec directories on disk, some already completed (e.g. 01_nightshift_afspec_models, 06_duckdb_reader_writer_split)
  2. Run af plan --spec 09_worktree_path_collision — DB correctly contains only spec 09
  3. Run af code
  4. After the first sync barrier fires (default: every 5 completed tasks), the hot-loader scans the full specs directory and injects specs 01 and 06 into the live graph
  5. Worktrees, branches, and sessions are created for those unrelated specs

Root Cause

The bug is a logic interaction between save_plan's full-table wipe and the hot-loader's Gate 4 ("already complete") check.

hot_load.py:294:

if are_all_tasks_done(spec_path) and _are_all_plan_nodes_done(spec.name, db_conn):

Gate 4 requires both conditions:

  • are_all_tasks_done() — checks tasks.json checkboxes
  • _are_all_plan_nodes_done() — checks plan_nodes table in DuckDB

But save_plan() (persistence.py:86-88) does DELETE FROM plan_nodes before inserting the filtered plan. So after af plan --spec 09, specs 01 and 06 have zero rows in plan_nodes. _are_all_plan_nodes_done() returns False when total == 0 (line 220: total > 0 and total == done), causing Gate 4 to fail open — treating "not in this plan" as "not done yet."

hot_load.py:306-329 (discover_new_specs):

all_specs = discover_specs(specs_dir)  # no filter_spec passed

The hot-loader scans the entire specs directory with no awareness of the filtered_spec metadata stored in plan_meta.

Impact

  • Wastes API tokens on sessions for already-completed specs
  • Creates stale worktrees and branches that collide with each other (multiple nodes sharing group 0 worktree paths)
  • Cascade of workspace setup failures (git worktree collisions) that trigger retry loops
  • Undermines the purpose of --spec filtering entirely

Observed Log Evidence

[INFO] agentfox.engine.barrier: Sync barrier 1 triggered at 5 completed tasks
[INFO] agentfox.engine.hot_load: Hot-loaded 2 new spec(s): 01_nightshift_afspec_models, 06_duckdb_reader_writer_split
[INFO] agentfox.graph.injection: Injected reviewer node '01_nightshift_afspec_models:0:reviewer:pre-review' at runtime
[INFO] agentfox.engine.graph_sync: State transition: node=01_nightshift_afspec_models:0:reviewer:drift-review from=pending to=in_progress reason=dispatched
[INFO] agentfox.engine.graph_sync: State transition: node=06_duckdb_reader_writer_split:0:reviewer:drift-review from=pending to=in_progress reason=dispatched

Suggested Fix

Two complementary changes:

  1. _are_all_plan_nodes_done() should return True (not False) when total == 0 and the plan was filtered. Alternatively, Gate 4 should treat "no plan nodes AND plan has filtered_spec set AND this spec is not the filtered spec" as "skip."

  2. discover_new_specs_gated() should respect filtered_spec from plan_meta. If the persisted plan has a filtered_spec, the hot-loader should only consider that spec as a candidate — or at minimum exclude specs not matching the filter.

Affected Files

  • packages/agentfox/agentfox/engine/hot_load.pydiscover_new_specs_gated(), _are_all_plan_nodes_done()
  • packages/agentfox/agentfox/graph/persistence.pyplan_meta.filtered_spec (already stored, just not consulted by hot-loader)

Metadata

Metadata

Assignees

No one assigned

    Labels

    af:fixIssues ready to be implementedbugSomething isn't workingpriority:highHigh priority

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions