Repository navigation
JSON output schema
Use --json to read SPAS operation results in scripts. Results go to stdout;
warnings and errors go to stderr as newline-delimited JSON. Help, shell
completion, and the root --version flag retain their text or script formats;
use spas version --json for build metadata as JSON.
Command payloads · Warnings · Errors · Schema contracts
See schema contracts for the version inventory, compatibility and bump rules, and the separate persisted link-state contract.
All JSON payloads emitted by SPAS include a root-level "schemaVersion" field:
{
"schemaVersion": 3
}-
Current Version:
3 -
Field Guarantee: The
"schemaVersion"field is guaranteed to be an integer present at the root of every JSON response on both stdout and stderr. -
Breaking Changes: Any breaking modification to top-level keys or semantic payload types will increment the
schemaVersion.
Schema 3 replaces portability-based collision classifications with
native_filesystem_alias, reflecting actual destination equivalence. Result,
error, and warning envelope fields are unchanged. The persisted link-state
schema is separate from this output schema.
When any command fails in --json mode, SPAS writes a structured error object to stderr and exits with the corresponding exit code:
{
"schemaVersion": 3,
"ok": false,
"error": {
"code": "decision_required",
"message": "local managed asset changes require approval to commit config/dev.json to the linked repository; provide --message"
}
}| Field | Type | Description |
|---|---|---|
schemaVersion |
integer |
JSON schema version (3). |
ok |
boolean |
Always false on error. |
error.code |
string |
Stable machine-readable error classification (e.g. not_linked, decision_required, path_conflict). |
error.message |
string |
Human-readable explanation of the failure. |
A command that already wrote its structured payload (for example spas doctor reporting findings) exits nonzero without emitting a second envelope.
Stdout retains each command's result payload. Stderr is a newline-delimited JSON stream: each warning is emitted immediately as a separate object, followed by the existing error envelope if a later step fails. A successful command may therefore have nonempty stderr. Decode stderr as a sequence of JSON objects, rather than as one document. No human prose, prompts, progress, or ANSI bytes are mixed into these streams.
{"schemaVersion":3,"warning":{"code":"public_repository","message":"Repository \"getspas/assets\" is publicly readable. Anyone may read managed assets stored there."}}warning contains exactly code (stable string) and message (readable string).
public_repository means the anonymous GitHub readability probe succeeded; it
requires no approval and does not change the exit status. Link and sync recheck
readability each time they reach that check. No warning is emitted for an
inconclusive probe; this does not assert private visibility. Dry-run and local
merge abort do not probe. Warnings already emitted remain available when a
later step fails, including a missing commit decision (exit 4), without adding a
success result to stdout.
exclusion_drift_repaired means an exclusion-writing command reconciled a
missing or unexpectedly edited owned block with authoritative link state.
Ordinary enrollment/removal changes stay quiet. The warning reports a completed
exclude-file write, not overall command success: a later effective-exclusion or
state-save failure may roll that write back. A retry can then warn again.
Rules whose ownership markers are no longer recognizable remain external and
can survive unlink. Doctor and dry-run do not repair or emit this warning.
{"schemaVersion":3,"warning":{"code":"exclusion_drift_repaired","message":"Reconciled unexpected changes to the owned exclusion block with link state."}}Warning records were introduced in schema 2, replacing schema 1's error-only stderr. Other result and error keys retain their existing shapes.
Conventions used below:
- Managed paths are workspace-relative, slash-separated strings.
- Keys marked conditional are present only under the stated condition.
- Array-typed fields serialize as empty arrays (
[]) rather thannullwhen empty.
{
"schemaVersion": 3,
"linked": true,
"publicWorkspace": "/path/to/project",
"privateRepository": "getspas/private-assets",
"networkAccess": true
}-
publicWorkspace— absolute path of the linked workspace root. -
networkAccess—trueif the visibility probe executed during this invocation;falsefor dry-run.
{
"schemaVersion": 3,
"action": "link",
"publicWorkspace": "/path/to/project",
"privateRepository": "getspas/private-assets",
"transport": "ssh",
"branch": "main",
"networkAccess": false
}-
branchis the empty string when no--branchwas provided (the branch is selected during first sync). - Dry-run performs no network access; the visibility probe is skipped.
{
"schemaVersion": 3,
"added": [
"config/dev.json",
"testdata/mock-api.json"
],
"canceledRemovals": [],
"skippedTrackedPaths": [],
"pendingSync": true
}-
added— paths newly enrolled by this command. -
canceledRemovals— pending removals cancelled because the path was re-added. -
skippedTrackedPaths— paths skipped because the project repository tracks them. -
pendingSync—truewhile enrolled additions awaitspas sync.
{
"schemaVersion": 3,
"action": "add",
"added": [
"config/dev.json"
],
"pendingAdds": [
"config/dev.json"
],
"canceledRemovals": [],
"skippedTrackedPaths": [],
"localExcludeWillChange": true,
"mergeProtection": "enable"
}-
pendingAdds— the complete pending-addition set after the command. -
localExcludeWillChange— whether the SPAS block in.git/info/excludewould be rewritten. -
mergeProtection— the resolved merge-protection action ("enable"or"skip").
{
"schemaVersion": 3,
"pendingRemovals": [
"config/dev.json"
],
"pendingSync": true
}Conditional keys:
-
refreshedRemovals(array, conditional) — already-pending removals whose recorded state this command refreshed; present only when non-empty. -
unenrolled(array, conditional) — never-synced pending additions that were unenrolled immediately; present only when non-empty. These files remain in the workspace and lose their SPAS exclusions. In human-readable mode, SPAS also warns about each path on stderr; JSON consumers should use this field.
{
"schemaVersion": 3,
"action": "remove",
"pendingAdds": [],
"pendingRemovals": [
"config/dev.json"
],
"refreshedRemovals": [],
"unenrolled": []
}{
"schemaVersion": 3,
"synchronized": true,
"privateCommitCreated": true,
"managedFiles": 2,
"skippedConflicts": [],
"publicRemovalsStaged": []
}Conditional keys:
-
deferredAdditions(array, conditional) — pending additions whose workspace file is currently missing; enrollment is kept, nothing was staged. Present only when non-empty. -
deferredRemovals(array, conditional) — pending removals whose workspace file changed after removal was requested; nothing was deleted. Present only when non-empty. -
recoveryCopies(string, conditional) — absolute directory that received recovery copies during this run. Present only when copies were written.
{
"schemaVersion": 3,
"synchronized": true,
"mergeContinued": true
}-
recoveryCopies(string, conditional) — as in the sync success payload.
One of three shapes, depending on the recorded recovery state:
{
"schemaVersion": 3,
"mergeAborted": true,
"gitNativeRecovery": true
}A Git-native merge without SPAS recovery state was aborted.
{
"schemaVersion": 3,
"mergeAborted": false,
"mergeRecoveryCleared": true
}SPAS merge recovery state was cleared; mergeAborted reports whether a Git merge was also aborted.
{
"schemaVersion": 3,
"mergeAborted": true,
"skippedPublicPaths": [],
"deferredPaths": []
}A full abort with workspace restoration. recoveryCopies (string, conditional) is added when copies were written.
Emitted when the managed checkout has not been initialized yet:
{
"schemaVersion": 3,
"action": "sync",
"networkRequired": true,
"privateInitialized": false,
"pendingAdds": [
"config/dev.json"
],
"pendingRemovals": []
}{
"schemaVersion": 3,
"action": "sync",
"networkRequired": false,
"privateInitialized": true,
"privateHead": "e6a1b2c3d4e5f60718293a4b5c6d7e8f9a0b1c2d",
"expectedPrivateHead": "e6a1b2c3d4e5f60718293a4b5c6d7e8f9a0b1c2d",
"privateClean": true,
"privateMergeInProgress": false,
"pendingRecovery": false,
"localChanges": [
{
"path": "config/dev.json",
"status": "M"
}
],
"commitApprovalRequired": true,
"commitMessageProvided": false,
"conflicts": [],
"pendingAdds": [],
"pendingRemovals": [],
"localExcludeWillChange": false,
"mergeProtection": {
"branch": "main",
"enabled": true,
"value": "--no-overwrite-ignore",
"present": true
}
}-
localChangesentries have the shape{"path": "...", "status": "..."}with status"A"(added),"M"(modified), or"D"(deleted). -
conflictsentries have the shape{"kind": "...", "publicPath": "...", "privatePath": "..."}wherekindis one oftracked_path,file_directory,native_filesystem_alias. - In
mergeProtection, the keysvalue,present, andambiguousare omitted when empty orfalse.
{
"schemaVersion": 3,
"linked": true,
"linkId": "lnk_3f9a2b4c17d0",
"publicBranch": "main",
"privateRepository": "getspas/private-assets",
"privateBranch": "main",
"privateInitialized": true,
"pendingAdds": [],
"pendingRemovals": [],
"managedFiles": 2,
"workspaceModified": [],
"workspaceMissing": [],
"privateCloneMissing": [],
"expectedPrivateHead": "e6a1b2c3d4e5f60718293a4b5c6d7e8f9a0b1c2d",
"actualPrivateHead": "e6a1b2c3d4e5f60718293a4b5c6d7e8f9a0b1c2d",
"privateHeadMismatch": false,
"privateAhead": 0,
"privateBehind": 0,
"pathConflicts": [],
"exclusionFailures": [],
"pendingRecovery": false,
"privateClean": true,
"mergeProtection": {
"branch": "main",
"enabled": true,
"value": "--no-overwrite-ignore",
"present": true
}
}-
linkId— the link identity,lnk_followed by 12 hexadecimal characters. -
publicWorkspaceandprivateClone(strings, conditional) — absolute paths, present only with--show-paths. -
publicBranch,privateBranch,expectedPrivateHead,actualPrivateHead,privateAhead,privateBehind, andprivateCleanare omitted when unknown — for example before initialization, on a detached HEAD, or when remote-tracking information is unavailable. -
mergeProtectionhas the same shape as in the sync dry-run payload.
{
"schemaVersion": 3,
"changedPaths": [
"config/dev.json",
"docs/team-notes.md"
]
}-
changedPathsis an empty array ([]) when no managed path differs.
{
"schemaVersion": 3,
"stagedPaths": [
"config/dev.json"
]
}-
stagedPathsis an empty array ([]) when nothing is staged.
See the Diff command reference for file-selection, alias, and staged-rename behavior shared by JSON and text output.
{
"schemaVersion": 3,
"healthy": true,
"checks": [
{
"name": "git",
"status": "ok",
"message": "git version 2.43.1"
},
{
"name": "data-dirs",
"status": "ok",
"message": "config and data directories are writable"
},
{
"name": "lock",
"status": "ok",
"message": "advisory file locking is functional"
},
{
"name": "worktrees",
"status": "ok",
"message": "single public worktree"
},
{
"name": "local-exclusions",
"status": "ok",
"message": "2 private path(s) effectively excluded"
}
],
"warnings": 0,
"errors": 0
}-
statusis one ofok,warning,error;healthyisfalsewhen any check reportserror. - Check inventory: the environment checks
git,data-dirs, andlockalways run. Outside a Git repository,workspaceis added with a warning status. Inside a Git repository,worktreesis added. In an unlinked workspace,link-stateis reported with a warning status. In a linked workspace the link checks also run:link-state,pending-recovery,case-policy,merge-protection,pull-mode,pending-ownership-transfers,path-ownership,local-exclusions, andexclude-block-integrity, plus — depending on clone state —interrupted-private-merge,remote-config,private-clone,expected-private-head,unsupported-private-file-types, orprivate-clone-initialization. - With
--json, one or more error checks exit1after the payload is written; no separate error envelope follows. - Warnings alone exit
0, including theworkspacewarning outside a Git repository and thelink-statewarning in an unlinked workspace.
{
"schemaVersion": 3,
"unlinked": true,
"keptFiles": true
}Conditional keys:
-
workspaceFilesNowVisibleToPublicGit(array, conditional) — managed paths whose exclusion rules were removed while the files stayed in the workspace; present only when files were kept and at least one path was affected. -
privateCloneRemoved(boolean, conditional) — present astrueonly when--remove-private-clonecompleted its cleanup.
{
"schemaVersion": 3,
"version": "1.0.0",
"commit": "0123456789abcdef0123456789abcdef01234567",
"date": "2026-08-30T00:00:00Z"
}-
version— semantic version string or"dev". -
commit— Git commit SHA (with optional-dirtysuffix) or"unknown". -
date— build timestamp (RFC 3339) or"unknown". - Without
--json,spas versionprints plain text:spas VERSION (commit COMMIT, built DATE).