Documentation / VFX-E-1501

VFX-E-1501

Title

RunInProgress

Explanation

Another run is already active, and only one run may be in flight per workspace at a time. This is the clearest retryable case in the whole catalogue: the identical call will succeed once the in-flight run finishes, with no other change needed.

When the server was started with --workspace <path>, the claim is a real lock on a real file — <root>/.vouchfx/runs/.lock — held for the duration of the run. It is therefore enforced across server processes, not just within one: two host connections, two editor windows, or a restarted server pointed at the same workspace all contend for the same claim, and the second one to ask is refused. Without --workspace there is no output directory to put that file in, so the claim is process-local and only overlapping calls to the same server see this code.

The error's details carries the active run's runId. When the run holding the claim belongs to the same server process you called, that id is exact — the server remembers what it minted. When the holder is a different process, the id is read back from the run registry: the entry sitting at running because of the very claim your call failed to take. Use it to correlate the refusal with the run you are waiting on (explain_run once it finishes), rather than guessing which run is in the way.

details can be absent, and in the cross-process case the id can name a run other than the one actually in the way. Both are bounded and neither makes the refusal itself wrong. There are three windows: between a holder claiming the lock and its registry entry being written (brief, clears as soon as the write lands — though it contains the holder's engine-CLI handshake, so on a holder's first call, or one stalled against a broken CLI, the message can say a run is in progress while the holder is still a moment away from actually starting one); between a holder recording its completion and releasing the lock (also brief); and — the one that does not clear on its own — a workspace holding more than 10,000 run directories, where the registry's scan bound can miss the live entry on every call. If you see a runId that has plainly already finished, that last case is the signal to retire old runs from <root>/.vouchfx/runs/. The server never skips a malformed newest entry in order to name an older one: if it cannot identify the active run it omits details rather than reporting the wrong run.

The rejection is immediate and is never queued. The server does not hold your tool call open waiting for someone else's run — spec §4.6 specifies a refusal, and a host is better placed than the server to decide how long to keep trying.

If you would rather not wait, cancel_run is the supported escape hatch — never a manual kill, and never deleting the lock file. Pass it the runId from details (or one from list_runs) and the active run is stopped gracefully: the engine's stdin is closed so it tears its own containers down, and it is killed only if that overruns the grace period. The claim is released as the run ends, and the cancelled run is recorded as cancelled with outcome Inconclusive rather than lost. Two answers mean it cannot be stopped from where you are asking: VFX-E-1507 (this server is not holding it — its message says whether another process has it, whether its completing record was lost, or whether this server's own different run masked the probe) and VFX-E-1508 (nothing is running it; the entry is residue, so the workspace is already free).

A lock whose holder no longer exists is not a problem you have to clear by hand. The claim is an operating-system-held file handle rather than a pid written into a file, so a server that is killed, crashes, or is force-quit has its claim released by the OS itself. There is no stale lock to detect.

Never delete .lock by hand — but the reason differs by platform, and on Linux/macOS the consequence is dangerous rather than merely useless.

  • On Windows, the file cannot be deleted while a run holds it: the server's handle is opened with no sharing at all, so the operating system denies the delete outright and the attempt simply fails. Deleting it while nothing is running achieves nothing — the server recreates it on the next run.
  • On Linux and macOS, the lock is an advisory flock record belonging to the file's inode, and the delete succeeds even while a run holds it. That silently breaks mutual exclusion: the next run_suite call creates a fresh file at the same path, gets a brand-new inode with no lock record on it, and starts a second concurrent run against the same workspace while the first is still going. This is measured behaviour, not a theoretical concern.

The .lock file is expected to persist between runs on every platform: it is created once and then never removed, by design. It is inert — the server never reads it and never asks whether it exists, only whether the operating system will grant it an exclusive handle. A leftover .lock in a quiet workspace is therefore normal, says nothing about whether a run is active, and is safe to ignore. Leave it alone.

Common causes

  • A previous run_suite call from the same or another client session is still executing.
  • A second MCP server process — another editor window, another agent, or a restarted server — is running a suite against the same workspace.
  • A client retried a run_suite call that appeared to hang, without realising the original call was still genuinely in flight.
  • Multiple agents/tools sharing one vouchfx-mcp server instance issuing overlapping run_suite calls.
  • A long-running suite (container pulls, health gates) that has not yet reached its timeout, so its claim is still legitimately held.

Fixes

  • Wait for the in-flight run to complete, then retry the identical call — this error is marked retryable for exactly that reason.
  • Or call cancel_run with the runId from details to stop the active run gracefully and free the claim; that is the supported escape hatch, and it never leaves containers behind.
  • Use the runId in details to find the run that is in the way: it is the one recorded as running under <root>/.vouchfx/runs/<runId>/, and get_run_status reports its full record.
  • Avoid issuing a second run_suite call before the first one's result (or a clear timeout) has been observed.
  • If runs are coordinated across multiple agents, serialise run_suite calls at the orchestration layer — this server rejects a concurrent call immediately rather than queueing it.
  • If two independent servers must be able to run at once, give them separate workspaces: the claim is scoped to <root>/.vouchfx/runs, so different roots never contend.
  • Never delete <root>/.vouchfx/runs/.lock to clear this error. On Windows the operating system denies the delete while a run holds it; on Linux and macOS the delete succeeds and silently breaks the claim, letting a second run start against the same workspace while the first is still going.
  • Expect <root>/.vouchfx/runs/.lock to exist even when nothing is running, on every platform — the file persists by design, is never read, and does not block anything.
  • If details names a runId that has plainly already finished, retire old run directories from <root>/.vouchfx/runs/: past 10,000 of them the registry's scan bound can stop reporting the live run.