Skip to content

[Feature]: rebuild fails permanently on ENOENT when git changes branches, blocking all MCP queries #645

Description

@Joonngy

Problem to solve

When madar watch is running and a git operation removes files (branch switch,
merge, rebase, reset), the incremental rebuild throws ENOENT and the watcher
latches into status: "failed" permanently. From that point serve --auto-refresh
rejects every query, even though a valid out/graph.json is still on disk.

A file disappearing mid-rebuild is a normal race with git — not an error condition.

Version: @lubab/madar@0.32.0 (global install, Node 24.13.0, macOS)

Reproduction

  1. Start madar watch on a repo, and an MCP client with serve --stdio --auto-refresh.

  2. git checkout <other-branch> where the target branch does not contain some file
    present on the current branch (in our case a *.spec.ts deleted between branches).

  3. The watcher logs:

    [madar watch] Rebuild failed: ENOENT: no such file or directory, open ''

  4. Every subsequent MCP call fails with:

    Madar auto-refresh cannot guarantee a fresh graph
    (status=idle, coverage=complete, policy=match,
    background_failure=[madar watch] Rebuild failed: ENOENT: ...)

  5. Restarting the client does not help — the watcher wedges again on the next
    git operation.

Observed out/watcher-state.json:

{
  "status": "failed",
  "coverage": "failed",
  "reconciliation_count": 7982,
  "failure_reason": "[madar watch] Rebuild failed: ENOENT: no such file or directory,
open '.../password-validation.util.spec.ts'"
}

In a day of normal branch work this made the MCP server unusable for hours at a time.

Root cause

dist/src/infrastructure/watch.js (~L426) has exactly one benign-error branch:

catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (message.includes('No supported files were found')) {
output.log('[madar watch] No supported files found - nothing to rebuild.');
return false;
}
output.error([madar watch] Rebuild failed: ${message}); // ENOENT lands here
return false;
}

There is no ENOENT handling anywhere in watch.js. The failure is then persisted
(~L820):

state.status = 'failed';
state.failure_reason = 'Automatic graph rebuild failed; the graph must not be treated
as fresh.';

and serve --auto-refresh refuses to answer while that flag is set.

Two separate problems compound here:

  1. A file vanishing during rebuild is treated as fatal, though it is expected
    whenever git rewrites the working tree.
  2. The failure is fail-closed — a stale-but-valid graph is withheld entirely.
    Without (2), (1) would degrade to "slightly stale" instead of "completely down".

Proposed solution

Suggested fixes

  1. Skip vanished files — treat error.code === 'ENOENT' during a rebuild as a
    skip-and-continue, not a failure. The next reconciliation picks up the new tree anyway.
  2. Don't latch failed — let a subsequent successful rebuild clear the flag, and
    retry on the next filesystem event rather than requiring manual
    madar generate . --update.
  3. Prefer fail-soft on read — when the graph is stale, respond with the last good
    graph plus a stale: true marker so callers can decide. Refusing all queries is a
    heavy penalty for a transient condition.

(1) alone fixes the reported symptom; (2) and (3) make the watcher resilient to the
whole class of "rebuild hit a transient error" situations.

Workaround

Drop --auto-refresh from the serve args and refresh the graph out-of-band
(we run madar generate . --update from a session hook). Without --auto-refresh
the gate is never consulted, so stale-but-valid results keep flowing.

Alternatives considered

No response

Area

extraction / parsers

What would success look like?

Graph update would be done successfully

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions