Synchronize the Claude plugin cache from target/claude/ via the consolidated sync engine
Install
npx skillscat add cuioss/plan-marshall/finalize-step-sync-plugin-cache Install via the SkillsCat registry.
Finalize Step — Sync Plugin Cache (project-local)
Project-local executor for project:finalize-step-sync-plugin-cache.
Invokes the consolidated sync.py engine in the project-local.claude/skills/sync-plugin-cache/ skill to mirror target/claude/
into the host plugin cache.
This step is project-local rather than a default: built-in for
the same reason as project:finalize-step-deploy-target: the cache-
sync only makes sense for this repo (the plan-marshall meta-project).
Consumer projects have nothing to publish, so they don't get this step
seeded into their marshal.json defaults.
This step runs on the main checkout post-merge, afterproject:finalize-step-deploy-target has regenerated target/claude/
from the merged source tree. Syncing the host cache here means the
next session boot and this sync read the same authoritative content.
Staleness guard
sync.py refuses to mirror target/claude/ into the cache unless the
emit sentinel proves the target tree is current. The sentinel is a
JSON file written by project:finalize-step-deploy-target attarget/claude/.emit-marker.json. It carries an ISO-8601emit_completed_at timestamp and a source_tree_fingerprint computed
from git's native ls-files / hash-object primitives overmarketplace/bundles/.
The guard refuses (exit code 2, status: error) in three cases:
| Case | Cause | summary_message shape |
|---|---|---|
| Sentinel absent | project:finalize-step-deploy-target was not run |
staleness_guard: sentinel missing or unreadable at {path} — run finalize-step-deploy-target first. |
| Sentinel unparseable / missing fingerprint | Corrupted or hand-edited sentinel | staleness_guard: sentinel ... is missing source_tree_fingerprint. |
| Fingerprint mismatch | Source tree changed since last emit | staleness_guard: source tree changed since last emit — re-run finalize-step-deploy-target. |
The Phase 6 ordering (project:finalize-step-deploy-target at 80 →project:finalize-step-sync-plugin-cache at 85) means the sentinel is
written immediately before this step reads it, so the guard normally
passes on the first try. A failure here points at deploy-target having
been skipped, or at concurrent edits to marketplace/bundles/ between
emit and sync (which would also invalidate the cached output).
The --skip-staleness-guard flag remains the escape hatch for tests and
recovery flows. Phase 6 never invokes it; only operators do, after
diagnosing why the sentinel is stale.
Ordering
The canonical Phase 6 ordering surrounding this step is:
default:branch-cleanup (70) →
project:finalize-step-deploy-target (80) →
project:finalize-step-sync-plugin-cache (85) →
default:record-metrics (990)order: 85 places this step immediately afterproject:finalize-step-deploy-target (so the cache mirrors the
just-regenerated target/claude/ content), post-branch-cleanup on the
main checkout. This step is the single project-level owner of on-main
executor regeneration: after the cache sync succeeds it regenerates.plan/execute-script.py against the freshly-synced cache (Execution
step 3). integrate_into_main performs the move-back only and does NOT
regenerate the executor — the executor stays a per-tree derived artifact
(ADR-002). Coupling regen to this step (CLAUDE.md's canonical "sync cache →
regenerate executor" sequence) closes the no-worktree staleness gap: the
regen runs in BOTH worktree and no-worktree finalize flows because it is
just a finalize step, not a move-back side effect.
Inputs
{plan_id}— required. Used for logging.
Execution
Inline-only — this step does NOT delegate to a Task agent. The sync
engine is a fast Python script with deterministic output.
1. Invoke the consolidated sync engine
python3 .claude/skills/sync-plugin-cache/scripts/sync.pyThe script returns a TOON document with status (success |partial | error), synced_count, failed_count,summary_message, and a synced[N]{bundle,version,status} table.
2. Parse the result
| Field | Meaning |
|---|---|
status: success |
All bundles synced; record outcome=done and use synced_count for the display detail |
status: partial |
Some bundles failed; record outcome=failed and surface summary_message in display_detail |
status: error |
Hard failure (no bundles synced); record outcome=failed and surface summary_message |
3. Regenerate the on-main executor (after a successful sync)
When the sync status is success, regenerate .plan/execute-script.py
against the freshly-synced host cache. This runs on the main checkout, so
the generator's cwd-relative resolution writes main's executor with mappings
that point at the just-synced cache — refreshing the on-main mapping after a
plan that changed the marketplace script set. This is the project-level,
meta-project-only owner of on-main executor regeneration; it replaces the
former integrate_into_main regen and runs in BOTH worktree and no-worktree
finalize flows (closing the no-worktree staleness gap).
python3 .plan/execute-script.py plan-marshall:tools-script-executor:generate_executor generateRegeneration is unconditional-after-successful-sync (always re-deriving
the executor from the fresh cache is cheap, deterministic, and eliminates the
staleness class without a gating heuristic) and non-fatal: a non-zero exit
or failure logs a WARN and the step still records its sync outcome — finalize
must not block on a mapping refresh.
python3 .plan/execute-script.py plan-marshall:manage-logging:manage-logging \
work --plan-id {plan_id} --level WARNING \
--message "[STATUS] (project:finalize-step-sync-plugin-cache) On-main executor regeneration failed (non-fatal); run /marshall-steward to recover"When the sync status is partial or error, SKIP regeneration — the cache
is incomplete, so regenerating against it would be wrong; the step records the
sync failure (Step 4) and an operator recovers.
3b. Reconcile the build daemon (after a successful sync)
The cache bump this step just performed is exactly what makes a runningmarshalld stale: the daemon is version-pinned to the OLD bundle copy while the
fresh cache now carries a newer one. After a success sync (and the executor
regen above), run the meta-project-only reconcile so an idle stale daemon is
upgraded to the verified pin and a busy one is left running with the deferral
recorded:
python3 .claude/skills/sync-plugin-cache/scripts/reconcile_daemon.pyThe script queries manage-build-server status and applies the idle-conditional
contract: idle-and-stale → upgrade (drain-then-start-verified); busy-and-stale
(anything in flight or queued) or an undeterminable running provenance →
defer (never drain a live build), leaving a readable reconcile-owed marker;
down-but-enrolled → plain start; already-current → no-op. An absent/disabled
build server (or a session without the executor) is a silent no-op via the
script's fail-open adapters, so this changes no shared-daemon behaviour and a
repository not using marshalld is unaffected.
Like the executor regen, the reconcile is non-fatal: it never blocks finalize.
Read its action / display_detail from the TOON and surface a one-line detail;
a defer is reported (the reconcile-owed marker persists it), never swallowed.
When the sync status is partial or error, SKIP the reconcile — the cache is
incomplete.
4. Mark step complete
python3 .plan/execute-script.py plan-marshall:manage-status:manage_status \
mark-step-done --plan-id {plan_id} --phase 6-finalize \
--step project:finalize-step-sync-plugin-cache \
--outcome {done|failed} \
--display-detail "{display_detail}"On status: success, {display_detail} is"{synced_count} bundles synced; on-main executor regenerated" (append" (regen failed — run /marshall-steward)" instead when the Step 3 regen
exited non-zero — the sync outcome is still done). Append the reconcile'sdisplay_detail from Step 3b when it did anything other than a plain no-op (e.g."; daemon upgraded" or "; daemon reconcile deferred (owed x2)"), so a
deferral is visible at the step level and not only in the marker.
On status: partial or status: error, surface the engine'ssummary_message field verbatim in --display-detail so the renderer
shows the underlying failure for triage.