Skip to content

_graph_pass_progress emits status=done even when the pass body raised (done→failed flicker) #459

Description

@HumanBean17

_graph_pass_progress emits status=done even when the pass body raised

Follow-up to #452 (surfaced during review of the parent-side terminal graph event).

Problem

_graph_pass_progress (src/java_codebase_rag/graph/build_ast_graph.py:140) wraps each graph pass 2–6 and emits a progress line in its finally:

# build_ast_graph.py:140-159
@contextlib.contextmanager
def _graph_pass_progress(pass_label: str, *, verbose: bool):
    ...
    _emit_graph_progress({"pass": pass_label, "status": "running"}, verbose=verbose)
    t0 = time.time()
    try:
        yield
    finally:
        elapsed = time.time() - t0
        _emit_graph_progress(
            {"pass": pass_label, "status": "done", "elapsed_s": f"{elapsed:.2f}"},
            verbose=verbose,
        )

The finally fires status=done unconditionally — including when the pass body raised. The relay routes this to the renderer, and IndexProgressRenderer.apply() treats any status=done for kind=graph as terminal (it keys on kind+status, not pass_), so it marks the task graph ✓ and stops it.

Impact

  1. Standalone (misleading ✓): if a pass body raises (e.g. a Ladybug write fails partway through pass 6), the child still reports pass=6/6 status=done → the renderer shows graph ✓ for a pass that did not succeed.
  2. Interaction with Fix graph progress state after interruption #452 (done→failed flicker): the child emits pass=6/6 status=done (renderer graph ✓), the exception propagates and the child exits non-zero, then run_build_ast_graph's parent-side finally correctly emits status=failed (renderer graph ✗). On TTY the description flickers (final state correct); on non-TTY / for programmatic on_progress consumers, two terminal lines/events are emitted (graph done then graph failed) for one failed build.

The parent's final failed state is the correct one — this issue is about the spurious done the child emits underneath it.

Suggested fix

Track whether the body raised and emit failed (or omit the terminal line) when it did:

    ok = False
    try:
        yield
        ok = True
    finally:
        status = "done" if ok else "failed"
        _emit_graph_progress(
            {"pass": pass_label, "status": status, "elapsed_s": f"{time.time() - t0:.2f}"},
            verbose=verbose,
        )

This makes the pass-level event truthful on its own and removes the -then- sequence for the pass-raises case.

Out of scope (not fixed by the above)

If pass 6 completes cleanly but a later step (post-pass-6 cleanup, db finalize, etc.) fails, the pass=6/6 status=done is arguably legitimate (pass 6 did finish) yet the renderer still treats it as terminal for the whole graph kind. Resolving that would require the renderer to distinguish a per-pass done (non-terminal for the kind) from an overall terminal done — a larger change to progress.py routing. Worth noting but separate from the unconditional-done bug above.

Context

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions