Skip to main content

Troubleshooting & FAQ

This page is organised around problems operators have actually run into, not around a list of error codes. Every entry below comes from a real report or incident. Each one gives the symptom, what is really going on, and the fix.

The CLI error reference at the bottom still covers exit codes, the exception taxonomy, --debug, and did-you-mean suggestions.


Before you start: ask Maestro first​

Three read-only commands answer most "what is wrong?" questions faster than reading logs by hand:

mso doctor # fleet self-check: dispatcher, crews, orders, voyages, hygiene
mso voyage why VOY-ID # why can't this voyage advance? names the one order to look at
mso order why ORDER-ID # why is this order not moving, and what are my options?

None of them changes anything. mso doctor works even with the dispatcher stopped, and exits non-zero if any finding is high or worse. The why commands print ranked options, and any option that would cost you something carries a risk: line.

Every install also ships the full troubleshooting reference on disk, with numbered entries (E01, E02, …) you can match by message:

mso docs troubleshooting # numbered error entries
mso docs when-a-voyage-is-stuck # every answer `mso order why` can give, and what to do
mso docs install-upgrade-uninstall

For a raw event trail, the dispatcher writes structured events to .mso/logs/lifecycle.log:

grep -E '\| (WARN|ERROR) ' .mso/logs/lifecycle.log

Installation and runtime​

mso version fails with cannot import name '__version__' from 'maestro' (unknown location)​

What happened in the field. An operator's mso version started failing with exactly this ImportError. The installed package itself was fine. The problem was a stray site-packages/maestro/ directory holding 136 MB of crew debris: build logs, patches and source files, with no __init__.py. Python resolved import maestro to that directory, a namespace package with no version, instead of the real install.

Why. Something wrote build output into the active Python environment's site-packages. In that incident it was a crew.

Fix.

  1. From a directory that has no maestro/ subfolder of its own, check what import maestro resolves to. python -c puts the current directory first on sys.path, so running it inside a source checkout imports that checkout instead of the install:
    cd ~
    python -c "import maestro, os; print(os.path.dirname(maestro.__file__), maestro.__version__)"
  2. Look inside the maestro/ directory under your site-packages. The real package contains __init__.py, cli.py, core/ and hooks/. Anything else (logs, patches, stray project files) is debris. If there is no __init__.py at all, the directory is not a package.
  3. Remove the debris directory and reinstall:
    pip install --force-reinstall --no-deps maestro-fleet
    mso version

Won't it happen again? Crews are now refused any write into the installed Maestro package or the active Python environment. You will see Path guard: refusing Write write to … — it resolves inside the INSTALLED Maestro package / active Python environment. That refusal is deliberate: point the write at the crew's worktree or $MAESTRO_TEMP instead.


pip show and mso version report different versions​

What happened in the field. pip show maestro reported one version while mso version reported another. The two commands were resolving against different site-packages directories: a user-scope install and a system-scope install were shadowing each other. A related trap: a CI job or service account running with PYTHONNOUSERSITE=1 or python -s silently skips the user scope and can pick up an older system copy.

Fix. Check what each command actually resolves to:

pip show maestro-fleet
pip show maestro # the pre-rename distribution name
mso version
python -c "import maestro, os; print(os.path.dirname(maestro.__file__), maestro.__version__)"

If they disagree, uninstall from every scope until both pip show commands report nothing, remove any orphaned maestro/ directory left in site-packages, then install once. The full procedure is in mso docs install-upgrade-uninstall.


I activated a licence, but Maestro still runs at community tier​

Symptom. Activation succeeded. Then mso licence status, and the first lines of mso dispatch, print:

⚠ Licence token failed verification (<reason>) — running at community tier.

You get community limits: 2 crews, only the INV/PLN/BLD/TST roles, no Hub, no resident LEAD agent.

What happened in the field. A licence token is verified against vendor signing keys bundled inside the installed package. A package built from a source checkout ships an empty key set by design, because signing keys are only added when an official release wheel is built. Installing that way silently wipes the trust root, and every activated licence on the machine downgrades to community. Some earlier published wheels also shipped with an empty key set. That is fixed: a release build now fails outright if its keys are missing.

Fix.

  1. Reinstall the official wheel. Use --force-reinstall: a plain pip install --upgrade maestro-fleet==X does nothing when version X is already installed, even if the installed copy is the keyless one, because pip treats the version as already satisfied.
    pip install --force-reinstall --no-deps maestro-fleet
    mso licence revalidate
    mso licence status # confirm the tier, and that no warning prints
  2. If the warning persists on an official wheel, your token may have been issued under a newer key than your installed version bundles. Upgrade, then contact maestrodevs.com/contact with the mso licence status output. That output does not include your key, so it is safe to share.

Can I tell before crews launch? Yes. mso dispatch checks at startup and logs a LICENCE_WARNING event to lifecycle.log if the licence resolves to a state worth knowing about, so a downgrade no longer shows up first as a Hub 403 or a crew cap.

Don't reach for MAESTRO_SKIP_AUTH

MAESTRO_SKIP_AUTH=1 exists for CI environments with no Claude account. It skips the licence check rather than fixing it. Don't set it on a live fleet to paper over a keyring problem.


Crews, branches and worktrees​

A crew's git command is refused: Branch guard: refusing …​

Why this exists. voyageBranch on an order is load-bearing, not advisory. During a customer retest, a mutation with no verified target branch was once silently allowed, and three commits landed on a stale branch. The guard now denies any git/gh mutation it cannot verify.

The guard runs before every crew shell command. The message tells you which rule fired:

Message containsWhat it meansWhat to do
current branch is X but this order's voyageBranch is YThe crew's checkout is on the wrong branch.Switch back to the voyage branch. Every crew commit belongs on that branch and nowhere else.
would leave the voyage branch / does not match the order's voyageBranchThe command explicitly targets another branch (git checkout other, git push origin other, gh pr create --head other).Stay on the voyage branch for the whole order.
no MAESTRO_SPRINT_BRANCH in the crew environmentThe guard has no verified target branch, so it refuses loose mutations.Usually the order has no voyageBranch and its voyage has no branch. Set one and re-dispatch.
refusing pull-request merge / tag / release mutation / package publishMerging, tagging, releasing and publishing are refused for every crew role, on every branch.By design. A human merges and releases; there is no crew-side workaround.
refusing raw gh api mutationA server-side REST mutation can't be checked against the voyage branch.Use git push or gh pr on the voyage branch instead.

Manual recovery. If you are the operator recovering by hand and you really do need to act outside the voyage branch, set MAESTRO_BRANCH_GUARD_BYPASS=1 in the parent shell for that session only. In a governed workspace, a bypass is refused unless the caller holds the break-glass capability and sets MAESTRO_BREAKGLASS_REASON. The grant is recorded in the audit log.


A crew's file write is refused: Path guard: refusing …​

Each crew works in its own git worktree ($MAESTRO_REPO_PATH), and the path guard keeps writes inside it:

Message containsWhat it means
OUTSIDE this crew's worktreeThe write targets the main checkout or a sibling crew's worktree. Writing there contaminates another tree. Confine writes to $MAESTRO_REPO_PATH (or $MAESTRO_TEMP).
inside the INSTALLED Maestro package / active Python environmentSee the shadowing incident above.
inside this crew's OWN worktree copy of .mso/The write used a relative .mso/… path. In a workspace that commits .mso/ to every branch, each worktree carries a throwaway mirror of .mso/, and a relative path lands in that mirror and is deleted when the worktree is pruned. A security review was lost this way twice before the guard existed. Write durable artefacts (handoffs, reports, plans) to the absolute $MAESTRO_ARTEFACT_ROOT/… path instead.

MAESTRO_PATH_GUARD_BYPASS=1 in the parent shell disables the path guard alone for a manual-recovery session. Don't leave it set: if a legitimate workflow keeps tripping the guard, report it.


A crew timed out, and the order went back in the queue​

Symptom. The lifecycle log shows REQUEUE, often followed by — work preserved on salvage/<order>-<sha> (N files, +A/-D).

What it means. A crew is killed when it goes silent for too long (default 10 minutes with no output), or when it hits its wall-clock ceiling. Before the order is requeued, its in-flight work is committed to a salvage/… branch, and the next crew starts from a tree that already contains that work. If a crew is killed with its role's deliverables already on disk, it is promoted to complete instead (DELIVERED_AT_TIMEOUT) and no retry is spent.

What to do. Usually nothing: mso order why ORDER-ID shows how many attempts remain before the order stalls. If one heavy order keeps timing out, raise that order's ceiling by setting timeoutMinutes in its JSON, rather than raising --timeout for the whole fleet.


The verify gate​

VERIFY_FAILED vs VERIFY_DEPS_FAILED: did my code fail, or did the tooling?​

If you set verifyCommand in workspace.json, Maestro runs it in the crew's worktree before pushing. A non-zero exit holds the order, so a broken build never reaches CI. The two events look alike and need opposite responses:

EventWhat it meansRemedy printedWhat to do
VERIFY_FAILEDThe gate ran and your code failed it.CODE FAILED — see log, fix, then mso order retry <ID>Open the full log named in the event (Full log: …), fix the code, retry.
VERIFY_DEPS_FAILEDThe gate could not run: the tools it invokes (tsc, the test runner) never became available.TOOL MISSING — install, then mso order retry <ID>Your code was never judged. Install the toolchain, then mso order retry <ID>. Don't go hunting for a code bug.
VERIFY_FRONTEND_SKIPPEDnpm isn't on PATH, so the auto-detected frontend step was skipped, loudly.—The order is not held. Install Node/npm if you want that step checked locally.

What happened in the field. A voyage was held overnight on what looked like a broken build: VERIFY_FAILED … 'tsc' is not recognized. Nothing was wrong with the code. A crew's own mid-build npm command had left a partial node_modules: the directory existed, but tsc did not. The gate took "node_modules exists" as "dependencies installed" and ran anyway. The same commit passed on retry in a fresh worktree. The gate now checks that the binaries actually resolve, installs when they don't, and reports a missing tool as VERIFY_DEPS_FAILED, not VERIFY_FAILED.

A second one. A cold npm ci on a slow Windows host finished in 16 minutes, and the order was held anyway because a fixed 15-minute install cap had already fired. The network was fine. The cap is now configurable (verify.frontendInstallTimeoutMinutes, default 30). If the cap fires but the tools resolve afterwards, the gate proceeds with a warning instead of holding.

Where is the full output? Only failures write a log: .mso/reports/verify/VERIFY-<ORDER-ID>-<timestamp>.log. It is written outside the crew's worktree, so it survives the crew being torn down. The inline excerpt in the event is a convenience; the log is the record.

Two failures that no longer hold an order. A failing test that is already red on the voyage branch without this crew's changes is reported as VERIFY_PREEXISTING and does not hold the order. A failure made up only of one or two vitest test timeouts, which pass on an immediate single-file re-run, is reported as VERIFY_TIMEOUT_RERUN_PASSED. Both are one-directional: if Maestro can't prove the failure is inherited or flaky, it stays VERIFY_FAILED.


Stalled and stuck orders​

An order is STALLED. How do I get it moving again?​

What it means. The fleet used up its retry budget (or the order was rejected on review) and gave up. It will not touch the order again by itself.

Step 1: find out which kind of stall it is.

mso order why ORDER-ID

Step 2: retry the right way. mso order retry is the only sanctioned way to reopen a stranded order. It clears the recorded terminal verdict, re-queues the order as PENDING, and by default resumes from the newest preserved salvage/… branch.

SituationCommand
It gave up after repeated timeouts or crashes, and preserved work existsmso order retry ORDER-ID
Two or more salvage branches exist and Maestro can't tell which is rightmso order retry ORDER-ID --salvage salvage/ORDER-ID-<sha>. Check the branch before you run it.
Preserved work is wrong or stale; start the role cleanmso order retry ORDER-ID --no-salvage
It was rejected on review (e.g. the Tester failed it with blocking defects)mso order retry ORDER-ID --role <previous role> --no-salvage. A plain retry re-runs the reviewer and gets rejected again.
The order is archived COMPLETE but you need it re-runmso order retry ORDER-ID --force

--salvage and --no-salvage are mutually exclusive. Add --reason "…" to record why you retried.

It was skipped, not stalled? mso order unskip ORDER-ID. Retrying a skipped order does nothing, because the skip list still vetoes it.

Won't the dispatcher just mark it STALLED again? Not if the cause is fixed. By default (completion.roleRejection.autoLap), a first rejection that comes with a FAIL QA report naming at least one blocking defect is sent back one role to be rebuilt, with the report attached. Only a second rejection stalls, and that is the signal that the order needs a human.


The voyage looks finished, but it won't close​

Symptom. Every crew is idle and nothing is running, but the voyage is never marked complete. The lifecycle log shows a WARN that voyage completion was withheld because orders are not COMPLETE.

What it means. A voyage is only finalised when every order in its manifest is COMPLETE. An order archived as STALLED, CONVERGENCE_FAILED or auto-serialised holds the whole voyage open. That is the point: an earlier version finalised a voyage that still had a stalled order in it.

Fix. mso voyage why VOY-ID names the blocking order. Recover it with mso order retry, or remove it from the voyage deliberately with mso order remove ORDER-ID --from-voyage VOY-ID.


The dispatcher​

The dispatcher exited on its own, with no crash and no error​

Expected. With all crews idle and no pending work for 30 minutes, the idle watchdog cleans up and exits (IDLE_REAP in the lifecycle log). To keep it running longer, raise completion.idleWatchdog.idleMinutes or set action to "warn" in workspace.json.

mso cleanup refuses: "a live dispatcher is running"​

mso cleanup force-kills crews and resets their orders, so it refuses to run underneath a live dispatcher. Run mso doctor first. If the dispatcher is healthy and just busy, don't clean up. If mso doctor reports it as WEDGED (process alive, heartbeat stale), mso cleanup --force is the escape hatch.

I merged the voyage PR, but the voyage is still "active"​

Maestro usually isn't running when you merge, so it never sees the merge happen. Run:

mso voyage reconcile --dry-run # preview
mso voyage reconcile # archive voyages whose PR has merged

The Hub's broom button does the same thing.

I want to stop the fleet without killing anything​

mso pause drains: no new work is claimed, running crews finish, and the dispatcher exits cleanly. mso resume picks up where it left off. Stopping now is still "stop the dispatcher, then mso cleanup".


CLI error reference​

The mso CLI (v4.3.9+) prints friendly error messages instead of raw Python tracebacks.

Exit codes​

CodeMeaning
0Success
1General error: configuration problem, licence issue, or unexpected runtime error
2Usage / argument error: mistyped subcommand, wrong flags, ambiguous input
3No work: the command ran cleanly but had nothing to do (empty queue, no matching data)
130Interrupted: Ctrl+C at the top-level mso process

Scripts can test $? directly. Exit 3 is not a failure. It means the command ran but found nothing to act on, which is useful in polling loops.

mso order why exits 0 either way

A clean "not blocked" answer is a successful answer. When scripting mso order why --json or mso voyage why --json, check the blocked field, not the exit code.

Exception taxonomy​

All user-facing errors derive from MaestroError. Each class carries a message, an optional hint with an actionable next step, and a fixed exit code.

MaestroError (exit 1)
├── MaestroConfigError (exit 1) — workspace/config/licence not set up
├── MaestroNoWorkError (exit 3) — nothing to do (empty queue, etc.)
└── MaestroUserError (exit 2) — bad user input or invocation mistake
└── MaestroAmbiguityError (exit 2) — multiple candidates; user must disambiguate

Example: MaestroConfigError

[Maestro] Could not find a Maestro workspace in this directory or any parent.
Hint:
Options:
1. Run this command from your workspace root (where .mso/ lives).
2. Use @PRJ to reference a registered project: mso status --project @MSO
3. Scaffold a new workspace: mso init --project MYPROJECT

Example: MaestroAmbiguityError: raised when a command matches several candidates. The message includes the exact command to run.

[Maestro] Multiple Claude projects match this workspace.
Hint:
Specify which one:
mso usage --project @MSO
or set MAESTRO_PROJECT=MSO.

Debug mode​

Unexpected errors (those outside the MaestroError hierarchy) produce a short message:

[Maestro] Unexpected error in 'dispatch': RuntimeError: <short message>
Re-run with --debug (or MAESTRO_DEBUG=1) for the full traceback.
Report this issue at: <issue tracker URL>

To see the full traceback:

mso --debug <command> [args]
# or
MAESTRO_DEBUG=1 mso <command> [args]

--debug also sets MAESTRO_DEBUG=1 in child processes, so the full traceback is visible at every level. Still seeing a raw Traceback (most recent call last): with no friendly message? Upgrade first, since older versions lacked the central error handler.

To report a bug, open an issue via github.com/Maestrodevs and include mso version plus the --debug traceback.

Did-you-mean suggestions​

A mistyped subcommand gets a suggestion:

$ mso statsu
[Maestro] argument command: invalid choice: 'statsu'
Did you mean: 'status'?
Run: mso status --help

A project acronym passed as a bare positional argument gets a flag suggestion:

$ mso usage MSO
[Maestro] 'usage' takes no positional argument 'MSO'.
Did you mean: mso usage --project MSO

Ctrl+C behaviour​

ContextBehaviour
mso status live monitorThe child monitor exits cleanly; mso catches KeyboardInterrupt and exits 0, with no traceback
Any mso commandCtrl+C exits with code 130 (POSIX SIGINT convention)

Still stuck?​

  • mso docs troubleshooting has the full numbered reference (E01 onwards), including PATH problems, API keys, stale locks and push failures not repeated here.
  • mso docs when-a-voyage-is-stuck covers every answer mso order why can give.
  • See the CLI reference for every command's flags.