Skip to content

Troubleshooting

This guide covers common issues you may encounter with LumenFlow and how to resolve them.

A claimed WU edit stays pending instead of releasing its code paths

Section titled “A claimed WU edit stays pending instead of releasing its code paths”

On an active worktree claim, wu:edit records the requested WU spec in the pending ledger. A strict reduction of the validated code_paths set is also published to canonical main. Inherited duplicate entries are ignored for the set comparison, and a successful publication writes the requested paths once in first-occurrence order.

Removing duplicate entries alone, adding or remapping a path, an empty or malformed scope, or a scope that no longer reduces the freshly fetched WU does not release the canonical overlap reservation. The final edited=yes line means the edit was recorded; it does not prove canonical publication. Look for the bounded Published canonical code_paths message to confirm a remote release. A Canonical code_paths not published message gives the pending reason and says the local WU spec will materialize at wu:done. If publication was refused or failed, retry only after resolving the reported claim, caller, or remote condition; a successful release is never inferred from the pending edit alone.

Prep reports a stale path from a pending WU edit

Section titled “Prep reports a stale path from a pending WU edit”

For an in-progress WU, a governed wu:edit may publish a strict code_paths reduction while the matching acceptance or test-path change remains pending until wu:done. Both the wu:prep pre-step and the spec:linter gate validate the materialized YAML plus the authorized pending field deltas. Ready and done WU records continue to use their materialized YAML.

If validation reports a path that a pending edit was meant to remove, check the pending spec update and its field list, then rerun wu:prep. Keep the acceptance and declared paths consistent in the pending edit. If the pending state cannot be read or projected, validation fails closed; resolve that state through the normal lifecycle flow rather than editing the protected WU YAML or bypassing the gate.

Recovery refuses an unsafe private namespace

Section titled “Recovery refuses an unsafe private namespace”

Recovery worktrees are stored outside the checkout in a private per-user namespace. A checkout may use shared group-write permissions such as 0775; recovery does not create or remove its temporary worktrees below .lumenflow/tmp. New namespace directories are private to your user, while ordinary Git-created 0755 descendants and 0644 files remain supported inside them.

On POSIX systems, recovery refuses a home directory that grants group or other users write access, and it preserves any non-private or unowned recovery namespace for inspection. It does not change checkout, home, or legacy .lumenflow/tmp permissions. If setup refuses, repair the permissions of the home/private namespace and retry. Windows uses the native per-user isolation primitive; Node mode bits alone do not establish ACL privacy.

Release reports an incoherent CLI artifact

Section titled “Release reports an incoherent CLI artifact”

The release entry is required to use a dist artifact whose provenance sidecar matches the CLI source, dependency/lockfile, and build configuration digests, the current Node major/ABI, and the expected entry path. A source edit, dependency or build-config change, missing/corrupt sidecar, or Node ABI change therefore causes a locked rebuild; the old dist is never executed.

Follow the exact remediation printed by the launcher (normally: pnpm install && pnpm build) and retry with Node 26. If the rebuild fails or the sidecar remains mismatched, release stops before mutating versions, tags, or registry state. Do not bypass this check with a timestamp assumption or a force flag.

CLI build cache reports an unusable or unactivatable build

Section titled “CLI build cache reports an unusable or unactivatable build”

Lifecycle commands that need a freshly built CLI resolve the build output by a digest of its inputs and keep it in a per-machine cache outside every checkout (in the per-user LumenFlow cache by default, moved with LUMENFLOW_CLI_DIST_CACHE — see the config reference). Every success claim is verified: the expected entry exists, the provenance sidecar is readable, every declared output file is present, and provenance matches the current inputs.

FreshDistIncompleteArtifactError names which of those failed:

  • A cached build could not be activated (a refused file removal, a full or read-only filesystem, a symlink inside the output directory). The command falls back to a real build on its own; no action is needed unless the real build then fails for the same environmental reason.
  • A peer build did not leave a usable artifact. The command rebuilds instead of trusting the peer’s completion. If it repeats, another process is writing the same output directory outside the lifecycle.
  • The build completed without replacing the output, so its provenance cannot be republished. When the build is the default scoped Turbo rebuild the command re-runs it once with the cache disabled on its own (for example after a raw turbo run build left the dist correct and the provenance stale), so a manual --force build is no longer needed. The case that still needs you is a custom build command, or a forced run that again changes nothing: an incremental build cannot replace an output it already considers current, so it can never repair a missing or corrupt provenance sidecar and every retry fails identically. Re-run the configured build with its cache disabled so it re-executes — the error prints the exact command. Do not delete a dist directory that other checkouts read through; that empties the artifact those checkouts depend on while your build runs.

The input digest covers the CLI and every workspace package it depends on. For each of those packages it covers every source file, not only src/: top-level files, tools/, tool-impl/ and similar directories count too. Test files outside src/, READMEs, dist and node_modules do not. An edit to any of these sources therefore triggers a rebuild. If you build the CLI by hand, build its dependencies with it (pnpm --filter '@hellmai/lumenflow-cli...' build). A CLI-only build leaves dependency packages running their previous code.

A poisoned or partially copied cache entry is rejected before activation rather than published over a working output, so a bad entry costs one rebuild, not a broken checkout. If you must discard the cache entirely, remove the cache root; it is rebuilt on demand and contains no state that is not reproducible from source.

The CLI hashes source, dependencies, and the Node ABI together, so sessions running different Node versions invalidate each other’s dist in a loop: pin the engine version on every command. Treat ERR_MODULE_NOT_FOUND under dist after a successful build as a stale build cache, not a code problem — remedy it with a forced rebuild of the package, not a debugging session.

Why pnpm <command> starts fast, and when it does not

Section titled “Why pnpm <command> starts fast, and when it does not”

Every pnpm lifecycle command goes through the LumenFlow CLI launcher. On an unchanged checkout the coherence check is a stat fingerprint of the files the digests read (path, size, mtime, ctime, inode), so it costs tens of milliseconds and spawns nothing. Any added, removed, edited or restored input changes the fingerprint and runs the full content hash. A file written within the last two seconds is never trusted from the cache, so the first command after an edit pays one full check. The Node major and ABI are checked on every call.

After a landing moves main, the next command rebuilds what is stale

Section titled “After a landing moves main, the next command rebuilds what is stale”

A landing fast-forwards your main checkout, which leaves the built packages (dist) behind the new sources. You do not need to run anything. The next pnpm <command> checks the dist of the CLI and of every workspace package it loads (the kernel, the software-delivery pack, and so on), rebuilds the stale ones once under the build lock, and carries on. Nothing is rebuilt when nothing is stale; the extra check costs about 65 ms. wu:done does the same rebuild before it opens the landing transaction, never inside it. If main moves again while a landing is already inside the transaction, the landing stops at once with a message naming the stale packages, releases the landing lock, and does not block anyone behind it; re-run pnpm wu:done and it rebuilds first.

The launcher also sets --max-old-space-size in its own environment so every process it spawns inherits it; it no longer starts a second Node process to do so. Set your own NODE_OPTIONS=--max-old-space-size=... to override it.

Why a read-only command still takes seconds, and what is cached

Section titled “Why a read-only command still takes seconds, and what is cached”

After the launcher, a command pays for its own module graph and reads. What is cached or deferred, and what remains:

  • The built-in pack’s import-boundary scan and integrity hash run once per pack content change (a stat fingerprint in the per-user pack-verification cache); editing any pack file re-verifies in full. Delete that cache to force a full verification.
  • Each ledger (wu-events.jsonl) line is validated against the event schema once: the lines the schema accepted are remembered by digest in the per-user ledger-validation cache (keyed by a digest of the schema, so a schema change starts empty). A changed line is always validated again. An unchanged ledger is read, decoded and digested once per process, and workspace.yaml and the pack manifests are parsed once and cached as JSON in the per-user yaml-parse cache keyed by their text. Delete either cache to force full validation and parsing.
  • --help, the licence gate and a read-only mem:inbox no longer import the pack registry barrel, the kernel runtime, the confinement schema, the WU document schema, simple-git or the ledger event schema. Lifecycle telemetry (heartbeat, duration tracking, branch-pr finaliser) loads only for a command that carries a session capability, opts into duration tracking, or is a lifecycle command.
  • Still paid by every verified command: the pack registry’s zod schemas (workspace spec, pack manifest, config). wu:status additionally waits on about a dozen git processes (two git status runs are the longest) and reads every ledger it needs; a very large wu-events.jsonl makes it slower in proportion.

Only wu:claim, wu:prep, wu:done and mem:checkpoint print a [signals] preview, and only for unread signals addressed to your WU, lane, initiative or session. Other commands print none. To see everything unread, run pnpm mem:inbox. Printing a preview does not mark anything read, and the wu:prep unread-directed-signal gate is unchanged.

Symptom:

> Format check... FAILED
>   src/utils/validator.ts needs formatting

Fix:

# Auto-fix formatting
pnpm format
# or
npx prettier --write .
# Or fix only the files listed by gates (copy from the gates output):
pnpm prettier --write "docs/guide.mdx"

# Then retry
pnpm wu:prep --id <wu-id>

Symptom:

> Lint check... FAILED
>   src/api/handler.ts:42 - Unexpected any

Fix:

  1. Read the error message to understand the issue
  2. Fix the code (don’t disable the rule unless necessary)
  3. Retry: pnpm wu:prep --id <wu-id>

Common lint fixes:

ErrorFix
no-explicit-anyAdd proper TypeScript types
no-unused-varsRemove the variable or prefix with _
exhaustive-depsAdd missing useEffect dependencies
no-restricted-importsUse the correct import path

For a fresh TypeScript project, initialize with the Node gate preset. Init installs ESLint and the TypeScript ESLint toolchain and writes an ESLint flat config. The configured lint gate uses pnpm exec, so it fails when the local executable is missing rather than downloading one. If doctor reports a missing configured gate tool, run the package manager’s install command, such as pnpm install, then rerun pnpm lumenflow:doctor.

Symptom:

> Type check... FAILED
> error TS2322: Type 'string' is not assignable to type 'number'

Fix:

  1. Fix the first error (later errors often cascade from earlier ones)
  2. Save and check again
  3. Repeat until clean

Symptom:

> Tests... FAILED
>   FAIL src/__tests__/auth.test.ts
>   Expected: "success"
>   Received: "error"

Fix:

  1. Run the failing test in isolation: pnpm test src/__tests__/auth.test.ts
  2. Debug: check test setup, mocks, and assertions
  3. Fix and retry

Common causes:

  • Mocks not reset between tests (use beforeEach)
  • Async tests not awaited
  • Environment differences (CI vs local)

If gates fail on code you didn’t change:

  1. Verify the failure existed before your changes

    # Re-run prep — it classifies each named gate against main without
    # mutating your worktree
    pnpm wu:prep --id <wu-id>
    # A gate the classifier marks pre-existing on main auto-skips; it does
    # not block your WU
  2. Use a named gate skip only when the gate is explicitly skippable

    pnpm wu:done --id <wu-id> \
      --skip-gate lane-health \
      --reason "Pre-existing lint failure in src/legacy.ts" \
      --fix-wu <other-wu-id>
  3. Create a follow-up WU to fix the issue

Symptom: WU shows in_progress but worktree is gone or work is done elsewhere.

Fix:

# Check current state
pnpm wu:status --id <wu-id>

# Recover based on situation
pnpm wu:recover --id <wu-id>

# Choose the appropriate action:
pnpm wu:recover --id <wu-id> --action restore  # Recreate missing active worktree from lane branch
pnpm wu:recover --id <wu-id> --action resume   # Resume recoverable WU state
pnpm wu:recover --id <wu-id> --action reconcile # Unstage only a proven stale completion tuple
pnpm wu:recover --id <wu-id> --action reset    # Reset ready/in_progress → ready
pnpm wu:recover --id <wu-id> --action cleanup  # Remove orphaned worktree

Use restore first when the WU is active or recently active and its lane branch still exists. It recreates the worktree without resetting WU status, branch, baseline/main metadata, or completion evidence. Use reset for orphaned ready / in_progress state only after restore is impossible or intentionally not wanted.

Current non-PR automatic wu:done publication builds and validates completion metadata in an isolated temporary worktree, then publishes the exact commit without mutating checked-out main. If remote acknowledgement is lost, it preserves that evidence; rerun the same command to prove and retire an accepted publication or rebuild from the latest remote main. For historical residue from older versions, run the read-only analyzer. If it proves that rollback completed but the index retains only the stale completion tuple, use --action reconcile. Reconcile preserves every worktree byte and refuses unrelated staged edits, unregistered worktrees, ambiguous projections, a busy Git index, or missing transaction evidence. Never substitute a raw reset or manual index edit.

Two claims from one checkout left a WU in_progress with no branch

Section titled “Two claims from one checkout left a WU in_progress with no branch”

Symptom: two wu:claim runs started together from the same checkout. One finished normally. The other printed fatal: ambiguous argument 'FETCH_HEAD': unknown revision and then said it was preserving the claim. The WU shows in_progress, but it has no lane branch, no claim ref and no worktree, so wu:recover --action restore and --action resume both refuse.

Cause: older versions recorded the claim commit by re-reading the checkout’s FETCH_HEAD after the push. Every fetch in a checkout rewrites that one file, so another claim’s fetch in progress could empty it after the claim commit was already published.

Current behaviour: concurrent claims from one checkout are supported. A claim records the exact commit it pushed and never reads FETCH_HEAD. If a claim fails after its canonical claim commit is published but before its lane branch and claim ref are published, it rolls the canonical record back to ready instead of preserving it. Rerun wu:claim afterwards.

Fix for a WU left behind by an older version:

pnpm wu:release --id <wu-id> --reason "ghost claim: no lane branch, claim ref or worktree"
pnpm wu:claim --id <wu-id> --lane "<lane>"

INTERRUPTED_DONE: the lane says done but main says in_progress

Section titled “INTERRUPTED_DONE: the lane says done but main says in_progress”

Symptom: wu:done stopped after it committed the done record on the lane branch and before it landed. wu:status shows main in_progress and the branch done; wu:repair --check reports INTERRUPTED_DONE. The lane holds unlanded work, so nothing auto-repairs or deletes it; never run plain wu:repair or wu:recover --action reset on it.

Fix (from the main checkout):

pnpm wu:release --id <wu-id> --keep-branch --reason "<why>"
pnpm wu:claim --id <wu-id> --lane "<lane>"
pnpm wu:rebase --id <wu-id>

Then run wu:prep from the worktree and the wu:done it prints. The release backs the lane tip up to backup/<wu-id>-interrupted-done-<sha>, removes only the unlanded done commit (what wu:done itself rolls back when a landing fails) and replays every later commit; the claim then reclaims the retained work. Without --keep-branch the release refuses.

wu:rebase after a passing wu:prep refuses as dirty

Section titled “wu:rebase after a passing wu:prep refuses as dirty”

Symptom: an older WU whose prep passed needs to catch up to main, and wu:rebase reports a dirty working tree. A passing prep leaves its rows in .lumenflow/skip-gates-audit.ndjson uncommitted.

Fix: none needed. wu:rebase takes custody of that unstaged append the way wu:done does: it holds the append out of the tree while Git runs and puts it back byte for byte, once, after success, a content-conflict pause, a failure and --abort (--continue and --abort do the same). Do not commit the audit rows to get a clean tree. A staged, rewritten or malformed audit change, a row for another WU, or any other modified or untracked file still refuses with the dirty-tree message before anything is fetched. A rebase invalidates the old prep evidence, so run wu:prep again on the rebased tip.

The same holds when you also ran mem:checkpoint: its row in the tracked wu-events.jsonl ledger is a second, legitimate kind of dirt, and wu:rebase holds and restores it together with the audit append, including on --continue and --abort. Only a compact checkpoint row exactly as the checkpoint command wrote it, for your own WU and carrying your claim session, is taken. The session match filters accidents; it is not authentication, and custody never commits a row. A row of another WU or session, a row with no session, any other event type, a tracked proof checkpoint left uncommitted, or a rewritten, truncated or staged ledger still refuses. When a refusal is unclear, the log carries the specific reason. The check never compares the row’s time with your clock. A paused rebase whose replayed commit only staged the ledger has nothing to hold, so --continue and --abort always work. If a run is killed between taking the rows out and putting them back, the next ordinary wu:rebase re-appends them from the retained copy under the Git common directory (lumenflow-generated-audits/<WU>/) and removes it, but only into the same claim and checkout; a copy from another claim stays put with a warning, and a copy naming a ledger this checkout lacks is renamed *.quarantined.

Taking over a stuck claim: no manual commit step

Section titled “Taking over a stuck claim: no manual commit step”

Symptom: a claim is stuck and a second session takes it over, for example with pnpm agent:session --wu <wu-id> --resume, a checkpoint or a recovery commit of its own, and then wu:release --keep-branch. Earlier releases refused for about half an hour with Recent successor activity guard failed, counting the taking-over session’s own commit and events as someone else’s.

Fix: none is needed. When the same-user claim authority holds (your Git identity and the live origin claim) the session you run wu:release from is the claim’s holder, and the activity guard ignores what that session and the session named in the claim record recorded. A ledger event recorded by any other session within the window still refuses, so the release stays safe against a live successor. The sequence is wu:release --keep-branch, then the ordinary wu:claim, then wu:brief; none of it asks you to commit a ledger line by hand.

wu:repair --claim --check likewise accepts the claim-auto checkpoint row that wu:claim records, if it is ever left staged: it tolerates exactly that row (and the claim’s own claim rows) for this WU, and refuses any other staged file or row.

A finished WU left its claim marker on origin (CLAIM_LANE_MISSING for a done WU)

Section titled “A finished WU left its claim marker on origin (CLAIM_LANE_MISSING for a done WU)”

Symptom: lumenflow:upgrade or another command prints CLAIM_LANE_MISSING: <wu-id> is done on the integration branch and its lane ... is gone, so the claim marker refs/lumenflow/claims/<wu-id> is a leftover of a finished WU, not a live claim; or wu:done ends with an error line saying the marker was NOT removed.

Fix: pnpm state:cleanup --claim-markers (add --dry-run to preview). It removes the marker of every WU that origin main records as done, whose claim generation equals the marker’s and whose lane branch is gone. Each delete leases the marker sha it observed, and an in-progress claim is never touched. lumenflow:upgrade runs the same cleanup before its claim checks and reports the WU ids it removed. wu:done retries a slow marker delete on longer bounds before it reports a failure. A CLAIM_LANE_MISSING message that ends in a git push is a live claim: follow it instead.

remote-diverged: reclaiming a kept branch whose origin lane copy differs

Section titled “remote-diverged: reclaiming a kept branch whose origin lane copy differs”

Symptom: after wu:release --keep-branch, wu:claim refuses Retained-work reclaim refused for <wu-id> (remote-diverged). The usual cause is a governed wu:rebase whose claim-source publication failed (for example at a pre-push hook): origin kept the pre-rebase lane tip, the kept branch carries the rebased commits, and the origin tip is no longer an ancestor.

Fix: none is needed when the kept branch is a rebase of the origin lane. Run the ordinary pnpm wu:claim --id <wu-id> --lane "<lane>". The claim compares the two sides by patch id, accepts origin-only commits that have a patch-identical twin on the kept branch (or that touch only lifecycle bookkeeping and the WU record), and publishes the kept branch under a lease on the origin tip it observed. No git reset and no force push is involved. The replaced origin tip stays reachable under the local backup ref refs/lumenflow/lane-backups/<WU-ID>/<sha12>.

Genuinely divergent lane: when origin holds commits with different content, the refusal names both tips and prints the governed command:

pnpm wu:claim --id <wu-id> --lane "<lane>" --replace-remote-lane

It reclaims from the kept branch, publishes it under the same lease and keeps the origin tip under the backup ref, so its commits can be cherry-picked afterwards. Nothing is discarded silently.

”Trusted policy content changed since this claim pinned it” during wu:done

Section titled “”Trusted policy content changed since this claim pinned it” during wu:done”

A trusted policy file (workspace.yaml, LUMENFLOW.md, LUMENFLOW.local.md) can change on the integration target while your wu:done waits for gates or the merge lock. wu:done checks the claim’s policy pin before it writes any done metadata, and again when it is about to publish. Both checks happen before the push, so the refusal always means your run published nothing:

  • Refused before the metadata commit: the lane, the index and the WU record are untouched.
  • Refused after the metadata commit (the change landed during the merge lock wait or the auto-rebase): wu:done rolls the lane back to its pre-done commit (“WORKTREE RESTORED, RERUN IS SAFE”). It no longer reports “publication outcome is unknown”.

Run pnpm wu:prep --id <wu-id> again: when main moved the policy and your branch changed no trusted policy file, prep re-pins the claim itself (audited as a claim_policy_refresh event with the reason main-moved), runs wu:brief, and re-runs. If your branch edits a trusted policy file, or prep still refuses, run the steps the box prints, in order. The refresh re-pins the claim and invalidates earlier prep evidence, so wu:prep runs again:

pnpm wu:repair --claim --id <wu-id> --integration-target --refresh-policy --reason "<why the policy changed>"
pnpm wu:rebase --id <wu-id>
pnpm wu:brief --id <wu-id>
pnpm wu:prep --id <wu-id>

then the wu:done command prep prints.

Your own generated audit append and mem:checkpoint rows in the claim worktree need no action: the refresh and wu:rebase hold them out of the tree and restore them byte-exactly once, and --check lists the paths it would hold. If the refresh refuses with “changes that are not its own lifecycle appends”, it names the path; nothing was published on a first run, so commit your own lane work with a hooked commit and re-run. When the refusal says the successor generation is already published, resolve the named path and re-run the same refresh to finish the sync. Running wu:rebase first is safe: it refuses naming the refresh and leaves those rows untouched.

If a refresh or rebase is killed while it holds your appends, the next wu:repair --refresh-policy or wu:rebase puts the generated audit append and the checkpoint rows back exactly once, including when the kill came after the claim marker moved to the successor generation. A retained copy that no longer matches its recorded digest is set aside (quarantined) and the run refuses before restoring anything; inspect it under the Git common directory (lumenflow-generated-audits/<WU>/) before re-running.

If the run instead refuses with “Retained lifecycle copies could not be inspected”, it could not list or read the retained copies of one kind (ledger or audit): the message names the kind, the path and the underlying error, and says that nothing was restored, quarantined or removed. Both kinds are inspected together, so neither is restored while the other cannot be read. Restore read access to the named path (a permission, a locked file, a failing disk or share) and re-run the same command; it then recovers every copy exactly once. A read failure never quarantines a copy, so waiting cannot make the refusal permanent. The same refusal covers a retention path of the wrong type (a file where the directory should be) and an audit copy whose receipt exists but whose content file is missing (a copy with no receipt at all is an interrupted hold that never held anything: it is left in place with a warning and does not block the run; it is not cleaned up automatically, the warning cannot tell an interrupted retention from a stripped receipt, and the next hold of the same append rewrites it): the named path shows what to inspect, and only a copy whose bytes were read and fail their recorded digest is ever quarantined.

A refresh that was published but never finished (the reported “Published claim source descriptor differs from the active complete binding”) is finished by a real wu:rebase (--dry-run only reports what it would finish) or wu:recover --action resume from the claim holder session, through the resume-only mode of the governed refresh: it never mints a new generation, whatever a marker or a record names. A refused finish changes nothing; if the lane sync committed and only the marker publication failed, re-running finishes it. A refresh interrupted after the lane sync (only the claim-source marker still names the previous generation) and a chain of several published refreshes are finished the same way. The finish is skipped while a governed content-conflict pause is active (use --continue or --abort). If the finish cannot complete, the refusal names the exact wu:repair --claim --integration-target --refresh-policy --id <wu-id> command that resumes it.

Run the refresh from the claim worktree or from the primary checkout (it finds the registered lane worktree itself). It refuses before publishing when the worktree has uncommitted changes, so commit or discard them first. If a sync still fails after the successor was published, the refusal says the refreshed generation is already published and the lane and claim-source marker are unfinished: fix that cause and re-run the same refresh command, which resumes. A lane left on the previous generation is read through the integration target by wu:rebase, wu:recover --action resume and the heartbeat only when it is a verified predecessor (same WU, same lane branch, a recorded refresh chain); anything else still refuses.

If an older run already left a done commit on the lane that is not on the target (the lane says done, the canonical record says in_progress, and wu:rebase or wu:done refuse with “Worktree claim binding state conflict between canonical and worktree ledgers” or CLAIM_DOCUMENTS_DISAGREE), run the same wu:repair --claim --id <wu-id> --integration-target (add --refresh-policy --reason when the policy also changed). As the claim holder it fetches the target, proves the done commit is not on it, and removes only that commit behind backup/<wu-id>-interrupted-done-<sha>, keeping every later commit (a retained-branch reclaim’s commits included). It changes nothing when the done commit is on the target, when the target cannot be fetched, or when you are not the holder. Only after that does it publish the claim lane; if the lane ledger still records the WU as done ahead of the target and no done commit can be located, it refuses with UNLANDED_DONE_UNPROVEN and publishes nothing.

A branch released by an older CLI can keep the done commit: an ordinary wu:claim of it unwinds that commit itself (same backup branch), so the reclaimed lane and the canonical claim both say in_progress and wu:rebase, wu:prep, wu:done and wu:recover proceed. A lane already reclaimed by an older CLI whose worktree ledger still carries a complete older than the canonical claim generation is accepted by wu:rebase; a complete newer than that claim is still refused.

A done WU’s only worktree registration points at a directory that no longer exists

Section titled “A done WU’s only worktree registration points at a directory that no longer exists”

Symptom: a scratch checkout of a done WU was deleted (it may have lived outside the worktrees directory), git worktree list shows the entry as prunable, and every wu:claim in the lane is refused with “orphan done WU”.

Fix: pnpm wu:repair --id <wu-id> removes exactly that one registration, after confirming the directory is absent, git reports the entry prunable, it is not locked, and the WU has its done stamp. The WU’s lane branch is deleted locally and on origin, as with any orphan repair. Other worktrees, branches, logs and unrelated stale registrations are left alone, so the broad wu:prune --execute is not needed. A locked registration is refused; unlock it first with git worktree unlock if the lock is stale.

Orphan-done worktree repair does not clear a suffixed re-adoption worktree

Section titled “Orphan-done worktree repair does not clear a suffixed re-adoption worktree”

Symptom: a done WU still has a leftover worktree, but the leftover directory uses a suffixed name from re-adoption (<lane-kebab>-<wu-id>-r2, -p0, …) rather than the canonical <lane-kebab>-<wu-id> name — for example a wu:done cleanup step that failed partway through an ownership handoff. wu:repair --id <wu-id> reports Successfully repaired, but wu:repair --id <wu-id> --check still reports ORPHAN_WORKTREE_DONE, and every wu:claim in the same lane is refused by the lane’s orphan pre-flight.

Fix: none needed — wu:repair resolves every worktree registered under git worktree list that matches the WU id (by directory basename or branch name), not only the canonical directory name, so a suffixed re-adoption worktree is found and removed in the same pass as the canonical one:

pnpm wu:repair --id <wu-id>
pnpm wu:repair --id <wu-id> --check   # confirms no ORPHAN_WORKTREE_DONE remains

Before removing a matched worktree, the repair quarantine-copies its memory store (if present) into the workspace’s quarantine area and records the copy location in a cleanup log, so a shared-memory node written inside the leftover worktree is never silently discarded. The repair reports success only once git worktree list no longer matches the WU id at all; if a matched worktree cannot be removed (for example it is git worktree locked), it reports a named failure or skip listing every path it examined instead of a false success.

Symptom: backlog.md says one status, WU YAML says another.

Fix:

# Check for state inconsistencies
pnpm wu:repair --all --check

# Fix detected inconsistencies (regenerates backlog.md and status.md from state store)
pnpm wu:repair --all

wu:release reports WU YAML / state-store divergence

Section titled “wu:release reports WU YAML / state-store divergence”

wu:release compares the WU YAML status with the event-sourced state store before any release mutation. When they disagree, it stops without changing the YAML, event log, derived docs, branch, or lane lock and names both statuses, for example:

WU YAML shows 'in_progress' but the state store shows 'ready' — the event log and WU YAML have diverged.
Run 'pnpm wu:repair --id <wu-id>' to reconcile.

Run the named repair, review its output, then rerun the intended release command. Do not use a release retry or a force flag to choose one state source over the other.

wu:rebase refuses a legacy claim with no claimed_branch

Section titled “wu:rebase refuses a legacy claim with no claimed_branch”

Symptom: wu:rebase fails closed because the WU documents carry no claimed_branch, and wu:repair --claim refuses first with:

Claim repair refuses conflicting local and canonical baseline_main_sha values

Claims minted before the branch-binding mechanism have no claimed_branch in either the canonical or the worktree document, and their local baseline_main_sha is often stale because the branch has not been rebased since it was claimed. The repair needed the rebase, and the rebase needed the repair.

Fix: run the claim repair as the recorded claim owner, with --base-dir naming the canonical main checkout. It now reconciles the binding and the baseline together:

# Audit first; --check makes no changes
pnpm wu:repair --claim --check --id <wu-id> \
  --base-dir /path/to/main --worktree <worktree-path>

# Apply, then confirm the governed rebase is unblocked
pnpm wu:repair --claim --id <wu-id> \
  --base-dir /path/to/main --worktree <worktree-path>
pnpm wu:rebase --id <wu-id> --dry-run

--check reports the absent binding and, when it applies, the divergent baseline as repairable. It never advertises a repair the apply path would reject: both resolve the canonical baseline through the same authority, so a well-formed but unresolvable object name fails --check closed instead of being reported repairable. The apply path writes the live attached branch into both documents and refreshes the local baseline_main_sha to the canonical value in the same commit, so a half-repaired claim document is never published.

Scope of this repair. It heals a claimed_branch that is absent from a local record over coherent canonical history. A canonical commit that was already published without claimed_branch is immutable history, and no repair heals it.

If a governed rebase is paused, --continue refuses before any write and wu:repair --claim refuses while the pause is active. Run pnpm wu:rebase --id <wu-id> --abort, then pnpm wu:repair --id <wu-id> --claim, then rerun pnpm wu:rebase --id <wu-id>. Unpaused, the repair and then the rebase complete.

Only a claim that predates integration-target binding is upgraded with pnpm wu:repair --id <wu-id> --claim --integration-target --check, then without --check.

Other worktrees do not interrupt the repair. The repair proves this claim’s own worktree registration (the records on its branch or at its path, with their branch and HEAD), not the whole git worktree list. A sibling worktree committing, being added or removed, or an unrelated micro-worktree no longer makes it refuse with authority changed. A moved own HEAD, a branch switch at the same tip, a second registration of the lane branch, or a changed local or remote lane ref still refuse before any write. A detached probe worktree that vanishes while the repair attests is re-listed a bounded number of times; a stale record that persists keeps the pnpm wu:prune --tmp-only remedy. Only transient git errors (lock contention) are retried; a mismatch never is.

Symptom:

ERROR: Lane "Framework: Core" already has <wu-id> in progress

Fix:

  1. Check if <wu-id> is truly in progress: pnpm wu:status --id <wu-id>
  2. If blocked, mark it: pnpm wu:block --id <wu-id> --reason "Waiting for API"
  3. If done, complete it: pnpm wu:done --id <wu-id>
  4. If orphaned, recover: pnpm wu:recover --id <wu-id> --action reset

Lane lock not auto-clearing despite dead PID

Section titled “Lane lock not auto-clearing despite dead PID”

Symptom:

ERROR: Lane "Framework: Core" is locked by <wu-id> (since 2026-02-19T10:00:00Z)

The lock holder’s process is no longer running, but the lock is not auto-cleared.

Explanation: Lane locks persist after the wu:claim process exits. A dead PID alone does not trigger auto-clearing — the lock must also be older than 2 hours (stale). This prevents a second claim from stealing a legitimately held lane.

Fix:

  1. If the lock is recent (less than 2 hours old), it is likely still valid. Complete or release the existing WU first.
  2. If the lock is genuinely abandoned, unlock manually:
pnpm wu:unlock-lane --lane "Framework: Core" --reason "Abandoned lock"
  1. Locks older than 2 hours with a dead PID will auto-clear on the next wu:claim attempt.

Symptom:

ERROR: Worktree has uncommitted changes. Cannot proceed with wu:done.

But git status shows only the WU event log (wu-events.jsonl) as modified (from a recent wu:brief run).

Cause: Running wu:brief records evidence to wu-events.jsonl. If other WUs merged to main and triggered auto-rebase, the rebase could fail because this workflow-generated file was dirty.

Fix: As of v3.19.0, wu:done auto-rebase automatically preserves and restores wu:brief evidence when wu-events.jsonl is the only dirty file. If you’re on an older version, commit the evidence file before retrying:

cd <worktree-path>
git add <path-to-wu-events.jsonl>
git commit -m "wu(<wu-id>): preserve wu:brief evidence"
cd /path/to/repo
pnpm wu:done --id <wu-id>

Ownership violation on wu:block or wu:release

Section titled “Ownership violation on wu:block or wu:release”

Symptom:

❌ SESSION OWNERSHIP VIOLATION: Active session is attached to another WU.

or:

❌ CLAIM OWNERSHIP VIOLATION: <wu-id> was claimed by a different session.

Cause: As of v3.19.0, state-mutating commands validate session ownership. You’re trying to modify a WU that belongs to another agent session.

Fix:

  1. Don’t modify another agent’s WU to free a lane. Wait for the owning WU to complete or block itself.
  2. If you have legitimate reason for cross-session recovery, use the override:
pnpm wu:block --id <wu-id> --override-owner --reason "Manual recovery per team decision"

All overrides are audited.

A claimed worktree cannot run wu:brief, wu:edit or wu:prep

Section titled “A claimed worktree cannot run wu:brief, wu:edit or wu:prep”

Symptom:

Claimed worktree <path> cannot run 'wu-brief' after a successful dependency install,
so it is not lifecycle-runnable and the claim was not completed.

What it means: wu:claim refused to report success for a worktree it could not prove is usable. This is the automatic bootstrap contract: a claim either hands you a worktree that runs the governed commands, or it does not complete.

wu:claim performs three steps, and only reports success after all three:

  1. Seed generated artifact roots from the main checkout, following the workspace dependency graph in deterministic topological order, so a dependency’s artifacts land before a dependent’s. An artifact is refused rather than copied when it resolves outside the main checkout or when its provenance records a different Node major or ABI than the claim is running. Mutable node_modules state is never copied.
  2. Install worktree-local dependencies with the immutable install for your package manager (pnpm install --frozen-lockfile, npm ci, …). This resolves dependencies; it does not build or validate the artifacts those dependencies expose.
  3. Verify by really running wu:brief --help, wu:edit --help and wu:prep --help inside the worktree, through the checkout-local dependency graph. The first of these builds a coherent local CLI dist if one is owed.

Step 3 exists because step 2 can report success over an incoherent artifact graph. Executing the command is the only check that distinguishes artifact presence from artifact coherence.

In a project that installs @hellmai/lumenflow-cli as a dependency, each command runs as the package’s own bin script under the current Node executable (node <package>/dist/wu-brief.js --help), never through the node_modules/.bin launcher, so the check behaves the same on Windows, macOS and Linux. The message ends with the exact command it ran and why it failed: the spawn error code and message when the process could not be started at all, or the exit code and the command’s own output when it started and failed.

Fix (a project that installs @hellmai/lumenflow-cli):

  1. Confirm @hellmai/lumenflow-cli is a declared dependency and reinstall dependencies in the main checkout with the project’s package manager.
  2. Run the command the message printed, by hand, from the worktree to see the same failure directly.
  3. Claim again.

Fix (the LumenFlow framework repository itself, which builds its CLI from source):

# Repair the main checkout on the Node major this repository pins, then claim again.
pnpm install && pnpm build
pnpm wu:claim --id <wu-id> --lane "Framework: Core"

The failed claim rolls back atomically, so no in_progress WU, lane lock, branch or worktree is left behind and nothing needs cleaning up first.

Note on --skip-setup: it opts out of all three steps. It is read-only by design — it seeds nothing, installs nothing, builds nothing and therefore certifies nothing — and a --skip-setup worktree is not certified to run wu:brief, wu:edit or wu:prep. It still fails closed when the main checkout it reuses has unsafe dependency links.

Note on --help: <command> --help skips the build when this checkout already has a CLI dist, but it never answers from another checkout’s dist. What help is being asked in a fresh worktree is whether this checkout can run the command; a file in the main checkout has never been evidence about this one. With no local dist, help builds one.

Symptom:

ERROR: Worktree not found for <wu-id>

Fix:

# If WU should still be in progress and the lane branch exists, restore first
pnpm wu:recover --id <wu-id> --action restore

# If restore is impossible and the WU is genuinely orphaned, reset + reclaim
pnpm wu:recover --id <wu-id> --action reset
pnpm wu:claim --id <wu-id> --lane "Framework: Core"

# If WU is done but worktree lingers
pnpm wu:recover --id <wu-id> --action cleanup

Symptom: You made changes in main instead of the worktree.

Fix:

# From main checkout, save a patch and list exactly which paths changed
git diff > /tmp/main-changes.patch
git diff --name-only > /tmp/main-changed-paths.txt

# Move to worktree, apply, and commit there
cd <worktree-path>
git apply /tmp/main-changes.patch
git add .
git commit -m "wu(<wu-id>): implement feature"

# Restore only the paths you just moved — never the whole tree
cd /path/to/main
git restore $(cat /tmp/main-changed-paths.txt)

Symptom: wu:rebase pauses after reporting ordinary content conflicts.

Fix:

cd <worktree-path>

# Edit and review only the conflict paths reported by wu:rebase
git add <resolved-conflict-paths>
pnpm wu:rebase --id <wu-id> --continue

The governed continuation verifies the active WU, lane branch, worktree and claim, integration target and SHA, replayed commit, paused HEAD, unresolved set, and staged path scope. It never selects ours or theirs. If one additional reviewed path must be staged, authorize it explicitly with repeatable --allow-staged <repo-relative-path> flags. To discard the attempt and restore the pre-rebase branch state, run pnpm wu:rebase --id <wu-id> --abort.

A paused wu:rebase refuses --continue because the policy pin is stale

Section titled “A paused wu:rebase refuses --continue because the policy pin is stale”

Symptom: a trusted policy file (workspace.yaml, LUMENFLOW.md, LUMENFLOW.local.md) changed on the integration target while the rebase was paused, and wu:rebase --continue refuses with “Trusted policy content changed since this claim pinned it”. wu:repair --claim --integration-target --refresh-policy also refuses, because the lane is mid-rebase.

Fix: end the pause with the governed abort, refresh the pin, and rebase again. --abort only restores the pre-rebase state and grants nothing, so a stale pin never blocks it; --continue creates lane commits, so it keeps the pin check. The abort clears the pause record itself, so never run git rebase --abort or move the record by hand.

pnpm wu:rebase --id WU-XXX --abort
pnpm wu:repair --claim --id WU-XXX --integration-target --refresh-policy --reason "<why the policy changed>"
pnpm wu:rebase --id WU-XXX

The only conflict is pnpm-lock.yaml (parallel dependency changes)

Section titled “The only conflict is pnpm-lock.yaml (parallel dependency changes)”

Symptom: another WU that changed dependencies landed first, and the only conflict in your rebase is pnpm-lock.yaml.

Fix: none needed. wu:done’s auto-rebase and wu:rebase regenerate the lockfile from the merged manifests, continue the rebase, and wu:done re-runs the gates on the result. You do not release, re-claim or carry files.

How it works:

  • It runs only when pnpm-lock.yaml is the single unmerged path. It starts from the integration target’s lockfile and runs pnpm install --lockfile-only --ignore-scripts --no-frozen-lockfile in your lane worktree, using the pnpm your repository pins in packageManager. No script runs and node_modules is not touched.
  • It checks that the result has no conflict markers, parses, passes a --frozen-lockfile check, and that no other file changed. Only then does it stage the lockfile and continue.
  • Each regeneration appends a lockfile-regeneration row to the git-ignored meta-rebase audit log.

If regeneration fails (registry unreachable, a manifest error, a timeout), wu:done aborts the rebase, leaves the worktree clean, and prints pnpm-lock.yaml was the only content conflict, but regenerating it from the merged manifests failed: <reason>. Fix the cause, then run pnpm wu:rebase --id <wu-id> from the worktree. If it records a pause, leave pnpm-lock.yaml unresolved, resolve and stage any other named paths, and run:

pnpm wu:rebase --id <wu-id> --continue

--continue regenerates the lockfile and finishes the rebase. If it fails again it refuses and the conflict stays intact. Any other conflict beside the lockfile, a lockfile deleted on one side, or a package manager other than pnpm keeps the behaviour described above: wu:done gives manual-fix guidance and wu:rebase pauses. Never resolve the lockfile by choosing a side.

A gate cannot find a dependency a landed WU added

Section titled “A gate cannot find a dependency a landed WU added”

Symptom: after another WU landed a dependency change, a gate fails with a missing module or an unresolved workspace package that your code did not change.

Fix: none needed. wu:done’s auto-rebase and wu:rebase compare package metadata across the rebase. When it changed, they run pnpm install --frozen-lockfile --offline in your worktree (online if the offline run fails), once, before any gate. When it did not change, no install runs.

If the install fails or times out, the command refuses and prints the exact repair command, so no gate runs on stale links:

pnpm -C <worktree> install --frozen-lockfile

Run it, then retry pnpm wu:done --id <wu-id> (or pnpm wu:prep). The rebase is already complete, so a retry does not reinstall by itself.

A fresh session of the assigned user may continue when its own Git identity and live origin claim ref verify. A different authenticated session of the assigned Git user uses the original unchanged valid wu:brief receipt when origin proof is unavailable, including a legacy claim without that ref. Rebase still requires the caller’s Git identity to match the canonical assigned_to owner:

pnpm wu:rebase --id <wu-id> --continue --handoff-receipt '<original-receipt>'

LUMENFLOW_HANDOFF_RECEIPT also supplies the receipt. Resolve and stage only the reported conflict paths first; additional staged paths require repeated --allow-staged flags. The CLI authenticates the pinned native pause before checking the receipt against the original branch commit’s content identity, then rechecks current canonical ownership, policy, protected scope, expiry, addressee and journal evidence. Successful continuation spends rebase once; a replay refuses. An explicit invalid receipt cannot fall back to same-user claim authority.

If replay pauses before a later tracked pending scope update, verification reads the original effective protected WU fields from the authenticated original tip’s WU YAML and validated pending journal, with its legacy tracked event ledger as fallback. A newer current private pending protected edit still invalidates the receipt. Malformed or unreadable historical metadata refuses; an absent optional journal or ledger alone is allowed.

The current canonical protected declaration must match the original tracked WU declaration before the historical pending snapshot applies. This conservative guard can refuse even a canonical protected edit made before receipt issuance when it differs from the original branch YAML. Metadata-only edits remain allowed. Do not restore metadata or re-brief while paused to force a match. Current wu:edit normally writes a private journal; intentionally committing that journal is compatibility test scaffolding, not a normal recovery step. The command syntax and help are unchanged.

Do not run wu:brief while paused, copy the claimant’s session ID, or alter the receipt or pause record. A brief commit changes the paused HEAD. If the pinned refs, WU/worktree identity or protected context changed, recovery refuses; inspect the named reason and use supported --abort when its guards still hold. Once continue or abort clears the pause, use normal briefing if needed.

Git objects are empty after an unclean shutdown (GIT_OBJECT_STORE_CORRUPT)

Section titled “Git objects are empty after an unclean shutdown (GIT_OBJECT_STORE_CORRUPT)”

Symptom: after a power loss or hard reboot, a lifecycle command (wu:claim, wu:prep, wu:done, wu:recover, wu:rebase, wu:status) stops with [GIT_OBJECT_STORE_CORRUPT]. The message names the damaged object ids and the quarantine directory. git’s own error: object file ... is empty / fatal: bad object follows it.

Cause: git’s default core.fsync=committed,-loose-object does not flush loose objects to disk, and git-config(1) warns that it “risks losing recent work in the event of an unclean system shutdown”. LumenFlow’s ledger commits, claims and rebases are loose-object writes.

Fix: follow the steps the error prints, in order. Nothing is deleted:

  1. Move each empty object file it lists into .git/lumenflow-object-quarantine/.
  2. Run git fetch --refetch origin (git 2.36 or later; older git: git fetch origin).
    • If it stops at bad object refs/heads/<branch>, that branch’s newest commits were never pushed and are lost.
    • Point the branch at its pushed state with git update-ref refs/heads/<branch> refs/remotes/origin/<branch>, then repeat this step.
  3. Run git fsck --connectivity-only. Every invalid reflog entry <id> or missing <type> <id> it prints is a commit that was never pushed. Its files are still in your working tree: run git status and commit them again.
  4. Run git fsck --connectivity-only --no-reflogs. It exits 0 once the object store is sound.

pnpm lumenflow:doctor runs the same scan on demand (zero-byte loose objects and an unresolvable HEAD) in its Git Object Durability section and prints the same steps.

Prevention: lumenflow init, pnpm lumenflow:upgrade and the pnpm lumenflow:enable repair set two repository-local keys. They need git 2.37.0 or later.

  • core.fsync=committed hardens loose and packed objects.
  • core.fsyncMethod=batch makes one disk flush per operation instead of one per object.

One write covers every lane worktree. A stricter value you already set (for example core.fsync=all or core.fsyncMethod=fsync, locally or globally) is kept. A weaker one is extended, never replaced. The cost on a claim is small, inside run-to-run noise.

pnpm lumenflow:doctor reports one of five states:

  • hardened
  • owner-set
  • opted-out
  • missing
  • git-too-old, which names the minimum version

To opt out:

pnpm config:set --key software_delivery.git.object_durability --value false

A git command was terminated by a signal (GIT_TERMINATED_BY_SIGNAL)

Section titled “A git command was terminated by a signal (GIT_TERMINATED_BY_SIGNAL)”

Symptom: a lifecycle command stops with git push was terminated by signal SIGPIPE before it reported an exit code (the subcommand and the signal vary). It often follows a long pause in a push, for example a slow pre-push hook.

Cause: git did not exit on its own; a signal stopped it. The usual cause is a dropped remote connection. SSH or HTTPS closes while git is waiting, and git dies writing to the closed pipe. Git never reported whether the operation completed, so LumenFlow treats it as failed rather than trusting it.

Fix: check what reached the remote, then run the same command again. For a push, compare git ls-remote origin <ref> with your local ref. Lifecycle commands are safe to rerun.

Profiling shows a 50 ms gap after quiet git commands

Section titled “Profiling shows a 50 ms gap after quiet git commands”

Symptom: in a profile or spawn-timing trace of a lifecycle command, git commands that print nothing (cat-file -e, check-ref-format, a clean status --porcelain, a quiet fetch) each finish about 50 ms after the git process has exited. A command that prints output has no gap.

Cause: in earlier releases, the git library LumenFlow uses (simple-git) waited a fixed 50 ms after any git command that wrote nothing, even after the process and its output had closed. A wu:claim runs about 40 such commands and a wu:done about 75.

Fix: upgrade. LumenFlow now reports each git command as soon as its process and output have closed. Exit codes, error messages, signal handling (see the entry above) and hook output behave exactly as before. If a hook leaves a background process holding git’s output open, the command still finishes when git exits, as before.

Symptom: wu:done exits non-zero or prints RESULT ... status=failure, but the lane branch’s commit is already on origin/main (check git log origin/main or the WU YAML’s status: done).

Cause: Once wu:done’s merge/push transaction has committed, every remaining step (worktree and branch cleanup, auto state cleanup, external work-item closeout) is advisory: it reports its own outcome on a POST-PUSH ADVISORY line and can never flip the command’s RESULT line or exit code. A concurrent sibling landing racing the auto-cleanup identity check, a merge lock held by another wu:done, or a worktree cleanup step can each fail independently after the landing is already durable.

Fix: Do not retry wu:done blindly — retrying an already-landed WU can duplicate cleanup work or trip an ownership guard. Read the printed POST-PUSH ADVISORY line(s) to see which step failed, and address that step directly:

# Stalled auto state cleanup
pnpm state:cleanup

# Leftover worktree from a failed cleanup step
pnpm wu:prune --execute

Symptom: wu:done stops with a failure box before anything lands (a failing hook, a formatter or docs:generate error, a staged-file check, a stale or unstamped dist inside the transaction), and the output starts with WU:DONE FAILED.

What happens: wu:done records the worktree’s pre-done state before it writes anything. When publication is proven absent, it restores HEAD, the original index bytes, pre-existing staged and partially staged changes, user file bytes and modes, untracked files, and metadata. Completion- introduced files are removed or restored. You do not unstage or restore anything by hand.

Fix: read the error in the box, fix its cause, and run the command the box prints:

pnpm wu:done --id <wu-id>

The box says WORKTREE RESTORED, RERUN IS SAFE when the restore completed. If it says WORKTREE NOT FULLY RESTORED, it lists what could not be restored. Run git -C <worktree> status and pnpm wu:recover --id <wu-id>, then rerun. Do not use a raw reset or a manual index edit.

wu:done failed after publication or with an unknown outcome

Section titled “wu:done failed after publication or with an unknown outcome”

Symptom: wu:done exits with a failure after its completion commit, and the output says Publication was verified or Publication outcome is unknown.

What happens: A verified publication is irreversible: the local completion commit is preserved and the output names --already-merged for reconciliation. If Git cannot verify the target, the outcome is unknown. This includes a negative ancestry check: it proves only that the captured lane SHA is not an ancestor, since a replayed completion may have landed with a different SHA. The local commit and worktree are preserved, and the command does not claim that nothing was published or that a plain rerun is safe.

Fix: For a verified publication, reconcile the completed WU:

pnpm wu:done --id <wu-id> --already-merged

For an unknown outcome, first check the exact lane commit against the integration target:

git -C <worktree> rev-parse HEAD
git -C <worktree> fetch origin main
git -C <worktree> merge-base --is-ancestor <lane-commit-sha> origin/main

If the ancestry check succeeds, use --already-merged. If it does not, inspect the target history and completion files for a replayed commit, and confirm publication is absent before retrying pnpm wu:done --id <wu-id>. Preserve the WU worktree and its completion files while the outcome is unresolved; do not reset or blindly replay the merge.

wu:done refuses: Spawn brief attestation mismatch

Section titled “wu:done refuses: Spawn brief attestation mismatch”

Symptom: wu:done for a delegated WU (wu:delegate, or wu:brief --parent-wu) refuses with Spawn brief attestation mismatch, even though the pickup was recorded and prep was green.

Cause: The delegation records two different hashes. The wu:brief evidence row hashes the brief before the lifecycle handoff authority is appended. The registry attestation promptHash hashes the final bytes after it. Entries written before the fix carry only promptHash, so the two never matched.

What wu:done verifies now (read-only, nothing is written):

  1. Claim-time pickup is present, and the attestation exists. Both are still required.
  2. Entries that carry contentHash (the pre-authority hash): accepted when it equals a recorded wu:brief evidence hash of the same WU. The evidence metadata uses content= (written by the current resolver) as well as the legacy hash=; either matches. promptHash stays recorded as the final-brief attestation.
  3. Entries without contentHash: wu:done searches the retained .logs/wu-delegate-<WU>.md and .logs/wu-brief-<WU>.md in the checkout it runs from and in the main checkout. It accepts only a candidate that hashes to promptHash with the same length AND whose pre-authority bytes (the authority block removed) hash to a recorded evidence row of the same WU. Reads are bounded (regular files up to 4 MiB); a directory, oversized or unreadable candidate is skipped and the search continues.
  4. Those filenames are reused by later legitimate briefs (your own first post-claim wu:brief, an explicit wu:brief --parent-wu). A candidate that does not match is not the delegated brief and is ignored; it is never treated as a contradiction. If no candidate matches, wu:done refuses.

Fix: No hand edit of the registry or journal and no --force is needed when the delegated brief is retained. The existing --force is an audited override (it records one), but it asserts rather than proves. There is no new repair writer; the existing delegation writer now records contentHash. Do not delete or move the retained .logs files, and do not re-run wu:delegate for a claimed WU, because that overwrites the retained file. Entries recorded after the fix need no retained file.

Stale local main: if the refusal reads Local main is behind origin, the delegation evidence is on origin/main but not in your checkout. Run pnpm git:sync-main and retry pnpm wu:done --id <wu-id>; no --force is needed. wu:done finds this by reading the ledger from the origin/main it already fetched. The Spawn brief attestation mismatch text appears only when origin does not hold the evidence either.

Limit: .logs/ is untracked and per checkout. An entry without contentHash can only be proven from the checkout (or its main checkout) where the delegation ran; a delegation run on another machine, or a deleted or overwritten file, cannot be proven and wu:done stays refused.

Symptom: wu:prune prints Unmanaged worktree: <path> (branch <name>), for example a worktree a desktop coding tool made under .claude/worktrees.

Cause: LumenFlow creates lane worktrees under its worktrees directory on lane/<lane>/<wu-id> branches and micro-worktrees on tmp/* branches. A worktree outside the worktrees directory on any other branch was made by something else, so wu:prune reports it once, does not count it as an error and never removes it. A non-lane branch inside the worktrees directory, or a lane/* worktree with no WU record, is still an error.

Fix: None needed. Remove it with the tool that created it if it is stale.

wu:done fails with “not a fast-forward”

Section titled “wu:done fails with “not a fast-forward””

Symptom:

ERROR: Cannot fast-forward merge lane/framework-core/<wu-id> to main

Cause: Main has new commits since you started your WU.

Fix:

cd <worktree-path>
pnpm wu:rebase --id <wu-id>
# If that command records a conflict pause, resolve and stage only its named paths,
# then run: pnpm wu:rebase --id <wu-id> --continue

# Return to main and retry
cd /path/to/repo
pnpm wu:done --id <wu-id>

wu:done fails with “Worktree has uncommitted changes”

Section titled “wu:done fails with “Worktree has uncommitted changes””

Symptom:

ERROR: Worktree has uncommitted changes. Cannot proceed with wu:done.

Cause: wu:done requires a clean, committed worktree before it can merge.

Fix:

cd <worktree-path>
git status
git add .
git commit -m "wu(<wu-id>): describe the change"

# Return to main and retry
cd /path/to/repo
pnpm wu:done --id <wu-id>

If the changes were accidental, restore the files, then retry.

Symptom:

ERROR: WU commit attempted from main checkout

Cause: You’re trying to commit WU work from the main directory.

Fix:

# Move to the worktree
cd <worktree-path>
git add .
git commit -m "wu(<wu-id>): implement feature"

Symptom:

BLOCKED: Commit message must reference <wu-id>

Fix: Include the WU ID in the commit message, for example:

git commit -m "wu(<wu-id>): document workflow friction"

Symptom:

WARNING: Branch is 15 commits behind origin/main

Fix:

cd <worktree-path>
pnpm wu:rebase --id <wu-id>
# If it records a conflict pause, resolve and stage only its named paths, then
# run: pnpm wu:rebase --id <wu-id> --continue (or --abort that recorded pause)
cd /path/to/repo
pnpm wu:done --id <wu-id>

Symptom:

⚠️  PARALLEL COMPLETIONS DETECTED

Cause: Other WUs merged to main while your WU was in progress.

Fix: Use the governed lane rebase from the WU worktree, then retry:

cd <worktree-path>
pnpm wu:rebase --id <wu-id> --dry-run
pnpm wu:rebase --id <wu-id>
# If content resolution pauses, edit and stage the reported paths, then run:
pnpm wu:rebase --id <wu-id> --continue
cd /path/to/repo
pnpm wu:done --id <wu-id>

If a long-lived worktree is pinned to an older CLI (such as 6.0.6 or 6.0.8), its wu:rebase may hard-abort on a spec-YAML content conflict because older versions did not pause for governed conflict resolution. Run the newer CLI from the main checkout and target the old worktree with --base-dir:

# Run the newer CLI from the main checkout; Git operations target the old worktree
cd /path/to/main
pnpm wu:rebase --id <wu-id> --base-dir <worktree-path>

# If it pauses, resolve only the paths reported by wu:rebase
cd <worktree-path>
git add <resolved-conflict-paths>

# Continue from the main checkout, still targeting the same worktree
cd /path/to/main
pnpm wu:rebase --id <wu-id> --base-dir <worktree-path> --continue

This uses the main checkout only for its newer CLI; --base-dir keeps the rebase, conflict state, and Git operations scoped to the worktree. Do not upgrade or reinstall the old worktree’s pinned dependencies to get the newer CLI, and do not resolve or stage paths that the command did not report. Use --dry-run first to inspect the rebase, or --abort with the same --base-dir to discard a paused rebase. Once it succeeds, run pnpm wu:prep in the worktree and finish with wu:done from main.

Rebase in an installed consumer with configured metadata paths

Section titled “Rebase in an installed consumer with configured metadata paths”

wu:rebase reads the target repository’s configured WU, backlog and status paths, including docs/tasks/wu. It auto-resolves only the active WU spec and configured lifecycle projections. A conflict in a different WU’s spec aborts the rebase. A present malformed workspace.yaml refuses; fix the reported configuration with the supported configuration commands before retrying.

Installed consumers need no Turbo or framework refresh script. The runtime spawns a fresh Node process from the executing software-delivery pack’s ./commands/wu/wu-rebase-baseline-refresh entry, resolved and confined to that installed pack. A consumer-owned script is never used for refresh. The entry is an internal executable boundary; calling it directly creates no grant or claim authority. Owner, caller, origin claim or receipt, worktree, branch and single-use continuation checks remain required.

Framework source targets retain their content-hash workspace build and artifact coherence checks. A failed build stops before refresh; a failed continuation names the WU and child exit status. Resolve that output and rerun wu:rebase, then use normal wu:prep and its printed wu:done command. Do not hand-edit a baseline, copy a session capability or run a consumer-provided refresh script.

When wu:done resolves governed metadata conflicts during an automatic rebase, LumenFlow appends an audit row to the meta-rebase audit log (meta-rebase-audit.ndjson under .lumenflow/). The file is append-only local evidence. It is intentionally ignored by Git and remains on disk across lumenflow init and lumenflow:upgrade.

A manually resolved wu:rebase content session appends a terminal row to the same file after a validated --continue or --abort. It includes old and final commits, target ref and SHA, all conflicted paths, explicitly authorized staged paths, resolver identity, timestamps, and the continued or aborted outcome. The ignored pause record (wu-rebase-conflict.json in the Software Delivery state namespace) remains available while the rebase is paused and is cleared after the terminal operation succeeds.

Do not commit the audit and do not delete it to satisfy gates. Incremental format checks exclude only this exact normalized runtime path because Prettier does not parse NDJSON; changed source files are still formatted and checked normally. If an older workspace shows the file as untracked, run lumenflow:upgrade to reconcile .gitignore. Reconciliation adds the canonical ignore entry without deleting or rewriting existing audit bytes.

--continue aborts with “no resolvable conflicts” and no other detail

Section titled “--continue aborts with “no resolvable conflicts” and no other detail”

If git rebase --continue itself fails for a real reason after you resolve a content conflict, wu:rebase now names the exact git or hook output in the abort error, instead of only the generic “rebase paused with no resolvable conflicts; aborted to avoid a half-rebased state”. The abort also always clears the pause record (wu-rebase-conflict.json in the Software Delivery state namespace), so the next wu:rebase attempt starts cleanly. Read the surfaced git/hook text to fix the underlying cause before retrying. A leftover pause record from before this fix is local, gitignored scratch state; it is safe to remove by hand.

Control-plane runtime files keep the checkout dirty

Section titled “Control-plane runtime files keep the checkout dirty”

The control-plane sidecar writes its latest-tick snapshot and durable outbox under .lumenflow/state/. These files are local runtime state, including when LumenFlow runs inside a nested workspace. Run pnpm lumenflow:upgrade --latest to reconcile the canonical root and nested ignore rules.

If an older installation already tracked .lumenflow/state/control-plane-sidecar-last-tick.json, upgrade removes the file from Git’s index without deleting or rewriting the working copy. It does not untrack repository-bound lifecycle evidence: wu-events.jsonl, migration-complete.json, and WU done stamps remain versioned.

lumenflow:upgrade refuses with .lumenflow/flow.log (changed) before publication

Section titled “lumenflow:upgrade refuses with .lumenflow/flow.log (changed) before publication”

Symptom: on a checkout whose state-namespace migration has not run (no migration-complete.json), the upgrade stops before it publishes with Consumer upgrade state conflict: legacy Software Delivery inputs changed since staging: .lumenflow/flow.log (changed). The upgrade’s own process, and any hook or agent running a CLI command meanwhile, appends log lines to the legacy .lumenflow/flow.log after staging.

Fix: none needed from 1.0.116. The staging baseline records the legacy flow log’s byte length and accepts the file when its first bytes are unchanged and everything after them is complete JSON-object lines. Those lines are added to the migrated flow.log before it is restored into your checkout, in order and exactly once, and the legacy file is never rewritten. A rewritten, truncated or reordered flow log, a partial or non-JSON line, a deleted file and a change to any other legacy input still stop the upgrade with the same message. On an older release, check that nothing else is writing to .lumenflow/flow.log, then re-run the upgrade. A line appended after the carry, or left behind when the upgrade stops before restoration, stays in the legacy flow.log: it is not lost, but it is not carried into the migrated log.

lumenflow:upgrade failed after publication

Section titled “lumenflow:upgrade failed after publication”

Symptom: the upgrade pushed chore: upgrade @lumenflow packages to <version>, then exited 1 with The <version> upgrade commit is published ... but reconciling local state failed after publication. Up to 1.0.11 the message was Consumer upgrade state conflict: legacy Software Delivery inputs changed since staging ... Both copies are preserved, most often because the consumer still tracked old .lumenflow/telemetry/* files that the upgrade itself untracks. The legacy baseline stores digests only, so no copy of earlier bytes exists. Since 1.0.116 one more cause is possible: the carry of legacy .lumenflow/flow.log lines into the staged snapshot refused (the legacy log no longer starts with the bytes the staging copy read, or the snapshot holds no flow.log). The recovery message names it, and the legacy file is untouched.

Fix: do not re-run the upgrade; its commit is complete. Run the commands it printed, in order:

pnpm git:sync-main                      # only when local main is not yet fast-forwarded
pnpm install --frozen-lockfile          # the install command the upgrade printed
node -p "require('./node_modules/@hellmai/lumenflow-cli/package.json').version"
pnpm lumenflow:doctor

The version check reads the installed manifest by path, because the CLI’s exports map does not expose package.json. The message names every changed path and the staged state snapshot it kept under the upgrade cache; compare that snapshot with .lumenflow/state/packs/software-delivery/ before copying any file it lacks. Old telemetry files the fast-forward removed from the working tree remain in Git history (git show <upgrade commit>^:<path>).

From 1.0.12 the check compares only legacy files copied from the main checkout, skips entirely once migration-complete.json exists, ignores agent-session and conductor writes under .lumenflow/state/, and lets live claims prune their lifecycle-enforcement/mutations/ receipts.

Software Delivery state (the WU event log, stamps, archive, merge lock, telemetry, flow log, incidents, and commands log) resolves under a pack-owned namespace instead of generic shared .lumenflow/<name> paths:

.lumenflow/state/packs/software-delivery/
├── wu-events.jsonl          # WU lifecycle event log
├── stamps/                  # WU completion stamps
├── archive/                 # archived WU events
├── merge.lock                # wu:done merge coordination lock
├── telemetry/                # gates, DORA, costs, tools, etc.
├── flow.log                  # WU flow events
├── incidents/                 # agent incident records
├── commands.log                # git command audit log
└── migration-complete.json    # durable migration sentinel (do not edit)

Agent session state (.lumenflow/sessions/) stays at the shared kernel path — it’s a cross-pack contract with the agent-runtime pack and is not part of this namespace.

Detection and migration: every CLI command checks for the sentinel file above before running main(). If it’s missing and legacy .lumenflow/state, .lumenflow/stamps, etc. exist, LumenFlow runs a one-way, atomic migration: it copies the legacy content into the namespace through a private staging directory, then promotes it with a single atomic rename, then records the sentinel (source digest, target schema version, per-category file counts — no secrets). The legacy directories are never deleted or written back to — they remain on disk as a historical record. Once the sentinel exists, subsequent CLI invocations skip re-verification (a single file-existence check), so steady-state startup cost is unaffected.

Recovery: the migration is fail-closed. If it’s interrupted mid-run, the next invocation either resumes cleanly (a crash before the atomic promote just leaves disposable scratch space that gets cleared) or finalizes cleanly (a crash after the promote but before the sentinel just needs the sentinel written — no data is re-copied or duplicated). If it detects the legacy source has changed since a prior successful migration (tampering or manual edits), or that the target namespace already contains data that doesn’t match what the legacy source would produce, it refuses to proceed and leaves both locations untouched — inspect .lumenflow/state/packs/software-delivery/ manually before retrying.

Removal: lumenflow:uninstall cleans up both the new namespace and any remaining legacy directories.

Configuration: workspace.yaml’s software_delivery.state.* fields (all except base, the kernel-shared .lumenflow root) may still be overridden, but overrides must resolve inside the pack namespace — an override that points outside .lumenflow/state/packs/software-delivery is rejected at startup instead of silently redirecting state to a shared writer path.

Symptom:

bash: wu-create: command not found

Fix:

# Ensure CLI is installed
pnpm add -D @hellmai/lumenflow-cli

# Use npx if not in PATH
npx wu-create --help

# Or add scripts to package.json

Symptom:

ERROR: Invalid workspace.yaml
  - lanes.definitions[0].name must match pattern "^[A-Za-z]+: [A-Za-z]+"

Fix:

  1. Check the error message for the specific field
  2. Compare with the Configuration Reference
  3. Fix the YAML syntax or values

Validate without running:

pnpm validate

Symptom:

ERROR: Cannot create checkpoint - no session active

Fix:

# Initialize memory for this WU
pnpm mem:init --wu <wu-id>

# Then checkpoint
pnpm mem:checkpoint --wu <wu-id>

Symptom:

⚠️  No checkpoints found for <wu-id> session.

Fix: Create checkpoints during long sessions:

pnpm mem:checkpoint --wu <wu-id>

LumenFlow commands include context-aware validation that checks your location, WU status, and git state before running. When validation fails, you’ll see an error with a copy-paste fix.

Symptom:

ERROR: WRONG_LOCATION - wu:done must be run from main checkout
FIX: pnpm -C /path/to/repo wu:done --id <wu-id>

Fix: Run the command from the correct location. wu:done runs from main; wu:prep runs from the worktree.

Symptom:

ERROR: WRONG_STATUS - <wu-id> is in 'ready' status, expected 'in_progress'

Fix: Claim the WU first: pnpm wu:claim --id <wu-id> --lane "Your Lane"

Symptom:

ERROR: GIT_DIRTY - Worktree has uncommitted changes

Fix: Commit your changes before running wu:prep:

cd <worktree-path>
git add .
git commit -m "wu(<wu-id>): describe changes"

You can adjust validation behavior in workspace.yaml:

software_delivery:
  experimental:

pnpm lumenflow:integrate is a deprecation no-op — it prints migration guidance and exits 0 without writing any files. LumenFlow no longer generates or manages AI-client-specific enforcement hooks. If your project has a .claude/hooks/ directory from an older LumenFlow version, it is a snapshot — your file now, never regenerated or touched by LumenFlow again. Adapting an enforcement-hook layer into your own tool is a task for your repository maintainers; see AI Coding Assistant Integrations for a copyable example.

In a Git repository, init and pnpm lumenflow:enable activate the generated .husky/pre-commit hook through local core.hooksPath=.husky only when Git has no configured hook path or existing default pre-commit hook. They preserve custom core.hooksPath values and existing default hooks. If your project uses a custom hook path, integrate the LumenFlow managed block there; doctor reports the active path and the required remediation when Git will not invoke LumenFlow. Doctor follows a husky v9 shim directory (core.hooksPath=.husky/_, generated by husky) to the real .husky/pre-commit it runs, and reports that file as the one it checked when the LumenFlow block is missing. Ordinary commits on main or master are blocked with WU workflow guidance once lanes are locked. While the lanes recorded in the committed workspace.yaml are still unconfigured or draft, the hook admits the documented setup commits on main, because wu:create cannot run before pnpm lane:lock. The hook reads the committed HEAD, so staging a workspace.yaml edit cannot reopen setup after lanes are locked. Temporary tmp/* lifecycle commits and the existing release exemption remain available to their governed commands.

If a hook blocks your operation, check:

  1. Are you in a worktree? Hooks block edits outside worktrees. Run pwd and verify.
  2. Do you have a claimed WU? Some hooks require an active WU for edits.
  3. Is the file in your code_paths? Hooks may restrict edits to scoped files.

See Agent Safety for the full enforcement architecture.

pnpm lumenflow:doctor reports lifecycle enforcement as enforced, non-compliant-disabled, missing, stale, tampered, or unavailable. For every remediable non-compliant state, run pnpm lumenflow:enable; it atomically writes the generic delegators and digest-bound manifest, preserving marker-external user bytes. unavailable means repository storage cannot be read safely: restore storage access before retrying. Only enforced is certifying. Disabled and uninstalled consumers cannot claim lifecycle certification.

Local remediation assumes the process follows repository policy.

Clean WU worktrees may not have the CLI built locally. Root LumenFlow package scripts should route through the LumenFlow CLI launcher so commands can use an existing main checkout build or build on demand instead of failing with MODULE_NOT_FOUND.

If a package script edit adds or changes LumenFlow CLI aliases, run:

pnpm verify:script-help
pnpm verify:script-help --run-probes

The live probes cover common agent entry points such as mem:create, orchestrate:monitor, lumenflow:commands, and lumenflow:setup-prereqs.

Before local database or local model workflows, run:

pnpm lumenflow:setup-prereqs
pnpm lumenflow:setup-prereqs --json

The report checks Supabase CLI, Docker, and Supabase local stack readiness when Supabase config or deps are detected. It also checks Ollama binary, OpenAI-compatible local API, and configured model readiness when Ollama deps or OLLAMA_* env vars are present.

Official setup references:

The prereq report intentionally omits local service keys and token-shaped values. Do not paste raw supabase start, .envrc, or provider-token output into issues, WU notes, or prompts.

Pack refuses every job after a CLI upgrade

Section titled “Pack refuses every job after a CLI upgrade”

Pack manifest version mismatch: expected <old>, got <new> on every governed command right after upgrading @hellmai/lumenflow-cli means workspace.yaml still carries the software-delivery pack pin the previous CLI wrote.

The CLI self-heals this automatically: a source: builtin pack pin is resolved from the CLI’s own bundled, unmodifiable packs directory, so a version or integrity drift against that pin is trusted and loaded rather than refused, and an advisory notes the drift. Run pnpm lumenflow:upgrade to clear the advisory by repinning workspace.yaml to the installed CLI’s bundled version through the same governed pin-write path the upgrade command already uses. If you installed the CLI directly (npm install @hellmai/lumenflow-cli@latest) rather than through lumenflow:upgrade, or a standalone Connected Compute runner picked up a new CLI version on its own, run lumenflow:upgrade once to write the repaired pin and silence the advisory for good.

Native addon fails to load on a runner (“compiled against a different Node.js version”)

Section titled “Native addon fails to load on a runner (“compiled against a different Node.js version”)”

A standalone runner install can carry a native addon (for example better-sqlite3) built for a different Node ABI than the Node binary the runner process runs under — most often after upgrading the runner’s Node version without rebuilding native dependencies, or after copying a cli directory between machines with different Node versions.

Run pnpm compute:doctor --experimental --runner-dir <path>: the report’s native addon ABI line names every mismatched package and the fix (npm rebuild <package>) before the runner accepts more work, instead of the runner re-claiming and re-failing the same assignment repeatedly.

The disk is filling with LumenFlow scratch

Section titled “The disk is filling with LumenFlow scratch”

Engine temp directories carry a name prefix and an ownership marker, and they are removed when the command ends. A crash can still leave some behind, and release artifacts accumulate per version.

Check: pnpm lumenflow:doctor prints a “Scratch and Disk” section with the size of each scratch root and the free space of its disk, and warns above software_delivery.cleanup.scratch_warn_gb (default 10) or below software_delivery.cleanup.disk_free_warn_gb (default 20).

Deep TMPDIR: when TMPDIR is so deep that tsx’s own socket path would pass the Unix limit, wu:prep’s docs-drift and parity checks run tsx with a short temporary directory and remove it afterwards, instead of failing with listen EINVAL.

Fix: pnpm state:cleanup --scratch --dry-run previews the sweep, then pnpm state:cleanup --scratch deletes engine-owned scratch older than software_delivery.cleanup.tmp_scratch_max_age_days (default 7; override with --scratch-max-age-days <n>). It also clears a crashed run’s leftovers from your per-user socket scratch root, with no age wait: only your own private entries whose owning process has ended, and only if that root passes the same ownership and sticky-parent check used to create it (an absent root is skipped silently; a root that exists and fails the check is not swept and the reason is reported). Only directories with both the engine prefix and the ownership marker are touched; sessions, worktrees, lifecycle roots and your own directories never are. A removed checkout’s git worktree registration is pruned with it. If a command refuses on a registration whose directory is already gone, pnpm wu:prune --tmp-only --execute removes the dangling scratch registrations. A dangling detached registration outside the worktrees root no longer blocks wu:claim or wu:rebase: it is skipped with a warning naming the path. Only a branchless one at the claim’s own path or under the worktrees root still refuses (the root is compared by real path, so a symlinked spelling counts as inside). While a rebase of the branch is paused, any dangling branchless record also refuses wu:rebase --continue until pnpm wu:prune --tmp-only --execute removes it. The engine’s scratch helper drops the registrations a removed scratch directory leaves dangling; a leftover from a session’s own scratch outside the engine still needs that prune. The check fails closed by design: a dangling detached record beneath a directory that cannot be traversed, or on Windows on a disconnected drive or an unreachable network host, still blocks claims until the record is pruned or the location is reachable again.

A successful release keeps only the newest software_delivery.cleanup.release_artifacts_keep (default 2) release-artifact versions. Set thresholds with pnpm config:set.

If these solutions don’t help:

  1. Check logs: read flow.log in the Software Delivery state namespace.
  2. Run with debug: DEBUG=lumenflow:* pnpm wu:done --id <wu-id>
  3. Review the FAQ for common pitfalls.

Include in your question:

  • The exact error message
  • Your workspace.yaml (redact secrets)
  • Output of pnpm wu:status --id <wu-id>
  • Your Node.js version: node --version

Windows guidance labels commands for cmd.exe, PowerShell and Git Bash. Copy only the command after the label for your shell. Git Bash paths use forward slashes and POSIX quoting. For a UNC directory, cmd.exe uses pushd, which maps a temporary drive; run popd when finished to restore the previous directory and release the mapping. For a path containing %, the cmd.exe line is a notice, not a command: use the PowerShell or Git Bash line, because cmd.exe can expand paired environment variables even inside quotes. Session-prefixed commands follow the same rule.

Git Bash’s //server/share spelling is shell-quoted, but MSYS native argument conversion has corner cases; shell parsing alone does not verify access to a network share or the installed pnpm launcher. See MSYS2 filesystem paths. When the local main checkout has not yet received the claim, a brief run in the matching worktree uses that actual worktree root in its next-command hint.