Skip to content

Mount invariant failure emits unbounded incident files into consumer repos #381

Description

@willwashburn

Problem

A persistent mount-root invariant violation causes Relayfile to write a new INCIDENT-<timestamp>.md on every poll cycle. When the invalid mount root is not a directory (for example, a symlink), the report falls back to the mount root’s parent — commonly the consumer repository root.

The invariant correctly fails closed, but the reporting path is unbounded and poll-frequency-driven. One unresolved operator condition becomes an endless stream of new files in another project’s working tree.

Live evidence

In AgentWorkforce/burn, .integrations is a symlink. A running Relayfile mount checked it on roughly five-minute intervals and wrote 11 untracked files into the repository root:

INCIDENT-20260729T181749Z.md through INCIDENT-20260729T190816Z.md

The reports are identical except for timestamp. The observed times are 18:17:49, 18:23:04, 18:28:03, 18:32:59, 18:37:52, 18:42:43, 18:48:05, 18:53:21, 18:58:20, 19:03:14, and 19:08:16 UTC.

This is independent of whether the symlink itself is a supported layout. A daemon should not create an unbounded number of recovery artifacts for one unchanged fault, especially in a consumer repository where they can be committed accidentally.

Root cause

internal/mountsync/invariants.go:

  • Syncer.assertMountRootInvariant() calls WriteIncidentReport() whenever CheckMountRootInvariant() fails.
  • Syncer.sync() invokes that assertion at the top of every sync cycle.
  • WriteIncidentReport() always chooses a timestamped filename.
  • When localRoot is not a directory, its candidate destination begins with filepath.Dir(localRoot), which is the repository root for a .integrations mount.

There is no deduplication, rate limit, “already reported” state, or stable incident identity.

Expected behavior

  • Preserve the first durable report for a distinct invariant violation.
  • Repeated checks of the same unchanged (localRoot, kind, reason) should update/reuse one report, rate-limit reminders, or log against the existing report rather than create new files.
  • A fallback report should not pollute the consumer repository root by default. Prefer a Relayfile-owned state/log directory or the system temp directory when .relay/ under the mount root is unavailable.
  • A materially changed violation should still produce a new report.

Regression coverage

Run multiple sync cycles against the same symlink/non-directory root and assert:

  1. the invariant remains failed closed;
  2. only one durable incident artifact is created for the unchanged condition;
  3. no INCIDENT-*.md files appear in the consumer project root.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions