| name | sk-pipeline-state |
| description | Defines the schema, storage layout, and recovery protocols for `pipeline-state.json`. Use when reading or writing pipeline state, resuming an interrupted run, or diagnosing a crashed orchestrator. |
| disable-model-invocation | true |
| user-invocable | false |
Pipeline State — Persistence & Recovery
Superpipelines use a structured JSON file to manage the lifecycle of multi-agent workflows. State is isolated from model behavior, ensuring runs are inspectable, resumable, and resilient to environment restarts. All state transitions follow an atomic write pattern to prevent corruption.
A structured JSON file (`pipeline-state.json`) representing the source of truth for a specific run.
The process of writing to a temporary file and renaming it to ensure file integrity.
A UUID v4 uniquely identifying a single execution instance of a pipeline.
The execution tier where the pipeline was scaffolded. Set once at run init; never updated.
The execution tier of the current or most-recent run. Re-detected on every resume; updated on cross-tier resume.
Append-only audit log of every cross-tier resume event. Never overwritten.
State Location
State must be persisted to `/superpipelines/temp/{P}/{runId}/pipeline-state.json`. Never store state within `${CLAUDE_PLUGIN_ROOT}`, as it is not persistent across updates.
Schema Definition
```json
{
"pipeline_id": "",
"pipeline_name": "
",
"plugin_version": "",
"scope_root_dir": "",
"run_id": "",
"started_at": "",
"pattern": "1 | 2 | 2b | 3 | 4 | 5",
"status": "running | completed | escalated | failed",
"current_phase": ,
"phases": [
{
"index": 0,
"name": "",
"status": "pending | running | done | failed",
"agent": "",
"outputs": [""],
"error": null
}
],
"metadata": {
"source_tier": "",
"runtime_tier": "",
"platform_profile": "",
"tier_changes": [
{ "from": "", "to": "", "at": "" }
],
"source_scope_root": "",
"isolation_warning": "",
"resolved_models": {
"": ""
},
"preference_files_consulted": {
"user_path": "",
"user_hash": "",
"workspace_path": "",
"workspace_hash": ""
},
"model_tiers_version_at_run": ""
}
}
```
Atomic Write Protocol
To prevent JSON corruption during concurrent operations or crashes, always use the following atomic write pattern:
1. Write the new state content to `pipeline-state.json.tmp`.
2. Move (rename) the temporary file to `pipeline-state.json`.
Encoding invariant (Q12)
State file MUST be UTF-8 without BOM. The byte at offset 0 MUST be 0x7B (the { character that opens a JSON object), NOT 0xEF (the first byte of the UTF-8 BOM sequence EF BB BF). Every implementation below guarantees this. A state file beginning with a BOM is unparseable by JSON.parse in Claude Code and produces a Parse Error → Corruption detected. Escalate to human recovery action. The protocol is shell-specific because naïve ports introduce BOMs silently.
Per-shell implementations
Bash / zsh:
TEMP_DIR="${SCOPE_ROOT}/superpipelines/temp/${PIPELINE_NAME}/${RUN_ID}"
mkdir -p "$TEMP_DIR"
printf '%s' "$NEW_STATE_JSON" > "${TEMP_DIR}/pipeline-state.json.tmp"
mv "${TEMP_DIR}/pipeline-state.json.tmp" "${TEMP_DIR}/pipeline-state.json"
PowerShell (Windows):
$TempDir = "$ScopeRoot/superpipelines/temp/$PipelineName/$RunId"
New-Item -ItemType Directory -Force -Path $TempDir | Out-Null
# The $false constructor argument disables BOM. Never use `Set-Content -Encoding UTF8` —
# in Windows PowerShell 5.1 that variant emits a BOM that breaks JSON.parse downstream.
[System.IO.File]::WriteAllText("$TempDir/pipeline-state.json.tmp", $NewStateJson, [System.Text.UTF8Encoding]::new($false))
Move-Item -Force "$TempDir/pipeline-state.json.tmp" "$TempDir/pipeline-state.json"
Node.js:
const fs = require('node:fs');
const path = require('node:path');
const tempDir = path.join(scopeRoot, 'superpipelines', 'temp', pipelineName, runId);
fs.mkdirSync(tempDir, { recursive: true });
fs.writeFileSync(path.join(tempDir, 'pipeline-state.json.tmp'), newStateJson, { encoding: 'utf8' });
fs.renameSync(path.join(tempDir, 'pipeline-state.json.tmp'), path.join(tempDir, 'pipeline-state.json'));
Byte-0 verification
A correct implementation can be verified by checking the first byte of the written file:
od -An -tx1 -N1 pipeline-state.json
Recovery & Resumption Rules
<recovery_rules>
| State Found | Required Action |
|---|
status: running (<1h old) | Active run detected; refuse to start a new instance. |
status: running (>1h old) | Treat as crashed. Prompt user to resume, restart, or abort. |
status: completed | Terminal state reached. Skip or archive. |
status: escalated/failed | Stop execution. Surface to human for manual intervention. |
| Parse Error | Corruption detected. Escalate to human; do NOT auto-resume. |
| </recovery_rules> | |
- **No Model Coupling**: Never use the model's native memory tool for pipeline state management; use structured JSON.
- **Atomic Renaming**: Direct writes to `pipeline-state.json` are forbidden.
- **Explicit Resumption**: NEVER auto-resume from an `escalated` or `failed` state without explicit user confirmation.
- **Backward Compatibility**: Pre-v2.0.0 state files carry `metadata.tier` (single field). On resume of an old state file: treat `metadata.tier` as `source_tier` when `metadata.source_tier` is absent; set `runtime_tier` to the re-detected current tier. New state writes MUST use `source_tier` and `runtime_tier`; never write `metadata.tier` in new state.
- **Version Stamping (Q12)**: `plugin_version` MUST be set at state initialization by reading the `version` field from the per-tier manifest at `platform_profile.extensions.version_manifest_path`. The Tier 1 manifest is `.claude-plugin/plugin.json`; other tiers point to their own manifest (Codex `.codex-plugin/plugin.json`, Cursor `.cursor-plugin/plugin.json`, OpenCode `opencode-plugin.json`, Antigravity `gemini-extension.json` until retirement). It is read-only after init and used by `running-a-pipeline` for compatibility advisory.
- **Portable scope_root (Q12 + #64 collapse)**: State files store `scope_root_dir` (the directory NAME) instead of the previous absolute path. On resume, the active scope_root absolute path is recomputed from the state file's own location. The recompute depth is **layout-dependent** because data-only drops the `superpipelines/` path infix:
- `layout:data` — state lives at `{scope_root}/temp/{P}/{runId}/pipeline-state.json` (`scope_root` = the `.superpipelines` DATA_ROOT). Recompute `scope_root = dirname^4(state_file_path)`; the sanity check `basename(scope_root) == scope_root_dir` yields `.superpipelines`.
- `layout:legacy` — state lives at `{scope_root}/superpipelines/temp/{P}/{runId}/pipeline-state.json` (`scope_root` = e.g. `.claude`). The extra `superpipelines/` infix puts `scope_root` one level higher than the data case: recompute `scope_root = dirname^5(state_file_path)` (vs `^4` for data); the sanity check `basename(scope_root) == scope_root_dir` yields e.g. `.claude`.
This survives workspace moves between machines, between WSL and native Windows, and between drives. If the basename sanity check fails for the pipeline's layout, the state file has been moved out of a recognized scope and resume MUST surface the inconsistency.
Reference Files
sk-pipeline-paths/SKILL.md — Scope root resolution.
sk-pipeline-patterns/SKILL.md — Execution pattern definitions.
running-a-pipeline/SKILL.md — Primary orchestrator workflow.