---
title: Master-Debt Auto-Fixer
---

# Master-Debt Auto-Fixer

## What it is

> **In development, hidden by default.** Reveal it with the **Settings → Lab →
> "Master-Debt Auto-Fixer"** toggle (or launch with
> `AMC_SHOW_MASTER_DEBT_AUTO_FIXER=1`). Off by default because the auto mode can
> spawn paid fix sessions on your behalf — see the two modes below.

A long-lived `master` branch accumulates **debt**: a test, lint rule,
type-check, or build that has been red for a while and isn't any one branch's
fault. When a session finishes a ready-to-merge run and discovers that some of
its failing checks **already exist on `master`**, the Master-Debt Auto-Fixer can
get that debt fixed instead of leaving it to rot.

It works per **file**. For each file that has pre-existing master debt, Omniscio holds
a single **claim** and does ONE of two things depending on your mode.

**It only spends money on debt that sticks around.** Before spawning any paid
fixer, Omniscio waits until the SAME failure shows up on **three separate**
ready-to-merge runs — its **durability gate**. Debt that master fixes on its own
before then, a flaky test that fails differently each run, or a one-off blip never
clears that bar, so none of them ever cost a fixer. Until a file is confirmed
durable it just sits as a cost-free "watching" entry on the tracker.

## Where to find it

**Settings → Lab → "Master-Debt Auto-Fixer"**. It is hidden by default because its automatic mode can start paid fix sessions on your behalf, so revealing it is a deliberate act — and so is leaving it in that mode.

## How it behaves

### The two modes

- **Ask first (the default).** Omniscio drops ONE dismissible **inbox card** per
  failing file — "Fix master-branch debt in `<file>` (N failing checks)". Approve
  it and Omniscio spawns ONE background fixer session for that file. Reject or dismiss
  it and nothing is spawned (the file becomes offerable again later).
- **Auto (opt-in).** Turn on the companion **"Auto-fix master debt"** Lab toggle
  and Omniscio skips the card — it spawns a fixer session per failing file directly.
  This is the mode that costs money without asking each time, which is why it is
  off by default and the whole feature is Lab-gated.

A **fixer session** is a normal background Claude session started in the
reporting session's own project. Its brief tells it the file and its failing
checks, frames the failure as pre-existing master debt (NOT the branch's fault),
**forbids weakening the check** (no `.skip` / `xfail` / deleting / relaxing the
test/lint/type/build check to go green), and asks it to root-cause the real
problem on its own worktree and take it to ready-to-merge.

When it's done — whether it fixed the debt or found there was **nothing left to
fix** (it won't fabricate a change) — the fixer **cleans up after itself**: once
its branch is at ready-to-merge it self-archives, but ONLY if nothing still needs
you (it isn't waiting on a question or approval and hasn't left a heads-up open).
Its worktree and branch are then reaped automatically once the branch lands or is
retired. If anything still needs your attention, it leaves the session visible
instead of hiding it.

### Safety — it cannot run away

Every part is bounded so a bad failure set can't spawn an army of sessions or
loop forever:

- **It only acts on durable debt.** A file must be reported as the SAME failure by
  three separate ready-to-merge runs before any fixer or card — transient, flaky,
  and one-off failures age out for free without ever spending. This is the biggest
  money-safety bound.
- **One fixer per file.** A file already being handled (an active claim or a
  pending approval card) is never claimed twice. Two things triaging the same
  file at once still produce exactly one claim and one fixer.
- **At most 3 fixers in flight.** Extra eligible files wait their turn — they're
  listed on the tracker (below), not acted on, until a slot frees.
- **Which engine a fixer runs on is a SETTING, and it defaults to DeepSeek V4.1 Flash.**
  Fixer work is mechanical and there can be many at once, so the cheapest capable engine is
  the shipped default. Change it at **Settings → Lab → "Master-debt fixer engine"** (it
  appears alongside the feature's own toggle). The choice is **reachability-gated**: if the
  engine you pick has no key on this machine and Omniscio's shared gateway cannot serve it,
  the fixer quietly falls back to the reporting project's own engine — the Worker tier
  (Sonnet) on a Claude project — so a missing key can never strand a fix. The engine is no
  longer inherited from the project, which is what previously put the same job on two
  different models in a single night.
- **At most 3 attempts, then it asks a human.** If spawning a fixer keeps
  failing, Omniscio retries a bounded number of times and then marks the file
  **"gave up"** rather than retrying forever.
- **Crash orphans are released.** If Omniscio crashes between claiming a file and
  actually starting the fixer, the stale claim is removed on the next pass so the
  file is eligible again (it is NOT permanently marked as given-up).
- **Stuck fixers are cleaned up.** A fixer that dies without fixing the debt, or
  an active claim older than 24h with the file still failing, is marked stalled;
  finished/given-up claims are pruned after 7 days.

A claim **resolves** either way the debt genuinely goes away: when a later ready-to-merge
run no longer lists the file (whether a fixer fixed it or master churn did, including
clearing the LAST failure of a kind), OR the moment the fixer itself reaches
ready-to-merge on its branch. The fixer now runs on an **Omniscio-tracked** worktree branch
(it is spawned with forced isolation), so Omniscio reads that branch's ready-to-merge tag and
credits the fixer as resolved — instead of the old behavior, where a finished fixer that
hadn't landed yet was mislabeled "stalled" until a later run happened to omit the file.

### The ledger and the tracker

Two files live in `~/.amc`:

- **`master-debt-claims.json`** — the machine-readable **ledger** of every claim
  (one per file) and its status. This is the dedup + safety record; Omniscio
  serializes all writes to it so two callers can't clobber each other, and a
  missing or corrupt file is simply treated as empty.
- **`master-debt-tracker.md`** — a human-readable **dashboard** rebuilt on every
  pass: a table of every open claim (file · failing checks · status · owner ·
  since), the files queued behind the 3-in-flight cap, and what was resolved in
  the last 7 days.

### Limitations (v1)

- Ready-to-merge detection reads the fixer's Omniscio-tracked worktree branch (fixers spawn
  with forced isolation). If a fixer ignores its brief and nests its OWN worktree, that
  branch's tag isn't the tracked one, so resolution falls back to report-omission (a
  later ready-to-merge run no longer listing the file).
- The triage caller is the Omniscio dev pipeline on the Omniscio repo; there is no
  general-purpose external intake.

## For agents

### How a session reports debt — the route

Omniscio's local control server exposes `POST /master-debt/triage`. The dev pipeline
(after a ready-to-merge run) submits the batch of failing checks it found to be
pre-existing on master: `{ v: 1, failures: [{ kind, file, ... }] }`. The route:

- requires the bearer token (401 otherwise) and shares the 10/min mutation rate
  limit (429),
- returns **403** unless the Lab feature is enabled (the flag is your consent
  that Omniscio may spawn paid fixers),
- validates the body strictly (≤ 200 failures; bad body → 400),
- uses the calling session's provenance (`X-AMC-Source-Session-Id`) to find a
  spawnable source project — and is a **benign no-op 200** (not an error) when the
  source session or its project can't host a spawn.

It applies immediately (no approval round-trip for the triage itself); the paid
spawn only happens under auto mode or an explicit card approval. **Scope (v1):**
"only the Omniscio repo" is enforced by the _caller_ (the ready-to-merge ping fires
only on the Omniscio repo), not by the route — the route is Omniscio-internal intake gated
by the feature flag plus the source project's spawnability.

### How it works

- The pure decision brain — [master-debt-service.ts](../../src/main/services/master-debt/master-debt-service.ts) —
  has every side effect injected (`spawn`, `raiseCard`, session-liveness,
  settings, `now`, `genId`), so it imports no Electron/DB and is fully
  unit-tested. The production singleton ([master-debt-singleton.ts](../../src/main/services/master-debt/master-debt-singleton.ts))
  wires it to the real ledger, the real fixer spawn, the real ask-mode inbox card,
  and `getSettings`.
- Two deterministic identity functions underpin the dedup: a **failure key**
  (`kind::file::discriminator`) makes the same failing check map to one row across
  runs, and a **file fingerprint** (sha1 over the file's sorted-unique failure
  keys) makes "the same debt" block re-triage while a CHANGED failure set becomes
  eligible again.
- **Durability gate.** `triage` records an `observed` sighting per reported debt and
  only spawns/cards a file once the SAME fingerprint reaches `CONFIRM_COUNT` (3)
  distinct reports. `observed` rows are non-occupying (no slot, no money) and age out
  if the debt stops recurring; a CHANGED fingerprint restarts confirmation.
- **Resolution is wired through the triage route:** after each triage the route calls
  `reconcile({ latestReport })`, and a claim resolves when its file is absent from
  that report. The periodic 5-min reconcile has NO report (so it only stales/prunes) —
  feeding the report at triage time is what makes resolution actually fire (it never
  did before, which is why claims used to pile up unresolved).
- The `master-debt.spawn` inbox action is **always approval-required** (it's in
  the non-toggleable set) — auto mode is a separate decision made at triage time,
  not a per-action approval toggle.
- A 5-minute background reconcile loop resolves / stales / abandons / prunes
  claims and releases orphans; it self-gates on the Lab flag each tick.

The full design and the test-locked invariants (one-fixer-per-file, claim-first,
the concurrency cap, the bounded give-up, orphan release, the route gate order)
live in the contract:
[master-debt-auto-fixer-contract.md](../../.claude/memory/contracts/master-debt-auto-fixer-contract.md).

## Related

- Omniscio’s unreleased-feature (“Lab”) gate — how the Lab
  toggle / `AMC_SHOW_*` gate hides this until it ships
- [session-provenance.md](session-provenance.md) — the `X-AMC-Source-Session-Id`
  origin the route uses to find the spawnable project
