Skip to main content

DOCTOR

swamp doctor runs diagnostics that verify a Swamp installation and repository's integrations are healthy. It has seven subcommands:

  • audit — verifies the AI-tool audit integration
  • datastores — checks datastore health, checks that a v3 checkout's records are readable, and detects foreign namespace contamination in giga-swamp repositories
  • extensions — verifies user-defined extensions load cleanly and inspects catalog state
  • install — checks installation health (binary path, ownership, writability, autoupdate status)
  • secrets — scans behavior definitions for cleartext sensitive global arguments and reports how to migrate each to a vault
  • vaults — scans behavior definitions for sensitive resource outputs and verifies a vault is configured to store them
  • workflows — checks workflow YAML files load cleanly

Global flags

All subcommands accept these flags. Only subcommand-specific flags are listed in each section below.

Flag Description
--json Output in JSON format (non-interactive).
--log Force flat log output (no interactive tree).
--log-level <level> Set log level (trace, debug, info, warning, error, fatal).
-q, --quiet Suppress non-essential output.
--no-telemetry Disable telemetry for this invocation.
--show-properties Show structured properties in log output.
--no-color Disable colored output.
--server Run against a remote swamp serve instance (env: SWAMP_SERVE_URL). See remote availability for limitations.
--token Server token in <name>.<secret> format; only with --server (overrides stored credentials and SWAMP_SERVER_TOKEN).
--repo-dir <dir> Repository directory (env: SWAMP_REPO_DIR).
-h, --help Show help.

Remote availability

Not all doctor subcommands are available remotely via --server:

Subcommand Remote status
secrets Available
vaults Available
workflows Available
audit Not available — requires local process execution to verify the AI-tool integration
datastores Available (without interactive --repair — serve is non-interactive)
extensions Available (without interactive --repair — serve is non-interactive)

doctor audit

Verifies that the AI-tool audit integration is healthy for the configured tool.

Flags

Flag Description
--tool <tool> Override the tool from .swamp.yaml. Values: claude, cursor, kiro, opencode, codex, copilot, pi, antigravity, none.

Exit codes

Code Condition
0 All audit checks pass.
1 One or more checks failed.

Output

Healthy

✓ binary-on-path  claude found at /Users/you/.local/bin/claude
✓ swamp-binary-on-path  swamp is on PATH at /opt/homebrew/bin/swamp
✓ agent-config-loadable  .claude/settings.local.json is present with both PostToolUse hooks wired
– default-agent-set  does not apply to claude
✓ recording-smoke-test  synthetic claude payload landed in today's audit JSONL

4 passed, 0 failed, 1 skipped — OVERALL: PASS

Failed check

When a check fails, a hint suggests a remediation:

✓ binary-on-path  cursor found at /Users/you/.local/bin/cursor
✓ swamp-binary-on-path  swamp is on PATH at /opt/homebrew/bin/swamp
✗ agent-config-loadable  .cursor/hooks.json is missing
    hint: Run `swamp init --tool cursor --force` to regenerate.
– default-agent-set  does not apply to cursor
✓ recording-smoke-test  synthetic cursor payload landed in today's audit JSONL

3 passed, 1 failed, 1 skipped — OVERALL: FAIL

For opencode, agent-config-loadable also fails on a .opencode/plugins/swamp-audit.ts written before OpenCode 2 support:

✗ agent-config-loadable  OpenCode plugin predates OpenCode 2 support (no default export with `setup`)
    hint: The rewritten plugin needs OpenCode 1.3.4 or later, or OpenCode 2 — update OpenCode first if it is older, then run `swamp init --tool opencode --force` to rewrite the plugin.

JSON shape

{
  "tool": "claude",
  "overallStatus": "pass",
  "checks": [
    {
      "name": "binary-on-path",
      "status": "pass",
      "message": "claude found at /Users/you/.local/bin/claude",
      "details": {
        "binary": "claude",
        "path": "/Users/you/.local/bin/claude"
      }
    },
    {
      "name": "agent-config-loadable",
      "status": "pass",
      "message": ".claude/settings.local.json is present with both PostToolUse hooks wired"
    },
    {
      "name": "default-agent-set",
      "status": "skip",
      "message": "does not apply to claude"
    },
    {
      "name": "recording-smoke-test",
      "status": "pass",
      "message": "synthetic claude payload landed in today's audit JSONL"
    }
  ]
}

overallStatus is "pass" when all checks pass, "fail" otherwise.

Each entry in checks includes:

  • name — check identifier
  • status — "pass", "fail", or "skip"
  • message — human-readable result
  • details — optional object with check-specific data
  • hint — present on failed checks when a remediation is available

Examples

swamp doctor audit                 # check the tool configured in .swamp.yaml
swamp doctor audit --tool kiro     # check a specific tool
swamp doctor audit --json          # machine-readable output for CI

doctor datastores

Checks that the configured datastore is healthy and flags any vault compatibility issues. For custom (remote) datastores with a namespace configured, also detects foreign namespace contamination — objects from other namespaces nested under the repository's own namespace prefix. For a v3 datastore, also runs the v3 content check. Also reports data names holding both kinds.

Flags

Flag Description
--verbose Show detailed output.
--retention Also scan storage for items older than their retention and show each kind's last pass.
--repair Preview foreign namespace contamination cleanup, or, for a v3 datastore whose format marker is lost, preview restoring the marker. Add -y to execute.
-y, --yes Execute the repair (without this, --repair shows a preview). --force / -f are also accepted.

Exit codes

Code Condition
0 Datastore is healthy and no contamination detected. A format marker repair also exits 0 on preview and on restore.
1 One or more health checks failed, including a v3 checkout that could not be inspected, or contamination detected.

Output

Healthy (filesystem datastore)

Checking datastore health…

Datastore type: filesystem
✓ Filesystem datastore at /path/to/repo/.swamp is healthy
✓ Every published data version is readable
✓ No data name holds versions of both kinds
✓ Catalog index covers all 7 record(s) on disk

Healthy (v3 datastore)

Checking datastore health…

Datastore type: filesystem
✓ Filesystem datastore at /path/to/v3store is healthy
✓ Catalog index covers all 0 record(s) on disk
✓ Every record of this v3 checkout is readable, and no content is held aside

Healthy (custom datastore with namespace)

Checking datastore health…

Datastore type: s3
✓ S3 datastore at s3://my-bucket is healthy
✓ No foreign namespace contamination detected

Contamination detected

When foreign namespace objects are found under the repository's namespace prefix:

Checking datastore health…

Datastore type: s3
✓ S3 datastore at s3://my-bucket is healthy
✗ Foreign namespace contamination detected

  2 foreign namespace(s) found under "my-namespace/":
    other-team/    148 objects
    staging/        23 objects

  Run `swamp doctor datastores --repair` to preview cleanup.

Data names holding both kinds

When a data name's history holds both file and resource versions:

✗ 1 data name(s) hold both file and resource versions, so the older kind is missing from swamp data list and type queries (spec/x). Read a hidden version with 'swamp data get <model> <name> --version <n>', then delete the name with 'swamp data delete' before writing it again.

Data names holding both kinds:
  spec/x: v1 file 'blob', v2 resource 'meta'

  Read a hidden version with 'swamp data get <model> <name> --version <n>', then delete the name with 'swamp data delete' before writing it again.

Repair preview (--repair)

Previews what the repair would clean up without executing:

Namespace contamination repair — DRY RUN

  Step 1: Delete foreign objects
    other-team/    148 objects to delete
    staging/        23 objects to delete

  Step 2: Rebuild remote index
  Step 3: Wipe local cache
  Step 4: Re-pull scoped data
  Step 5: Invalidate workflow run and catalog indexes

  Total: 171 foreign objects to remove

  Run `swamp doctor datastores --repair -y` to execute.

Repair executed (--repair -y)

Namespace contamination repair

  Step 1: Delete foreign objects          ✓ 171 objects deleted
  Step 2: Rebuild remote index            ✓
  Step 3: Wipe local cache                ✓
  Step 4: Re-pull scoped data             ✓
  Step 5: Invalidate workflow run and catalog indexes  ✓

  Repair complete. 3 workflow run(s) recovered.

If repair fails mid-way, the error message identifies which step failed and how to restore:

Namespace contamination repair

  Step 1: Delete foreign objects          ✓ 171 objects deleted
  Step 2: Rebuild remote index            ✗ Failed: connection timeout

  To restore local state, run: swamp datastore sync --pull

JSON shape

{
  "overallStatus": "pass",
  "datastoreType": "filesystem",
  "isCustom": false,
  "healthFindings": [
    {
      "check": "health",
      "passed": true,
      "message": "Filesystem datastore at /path/to/repo/.swamp is healthy"
    },
    {
      "check": "unreadable_versions",
      "passed": true,
      "message": "Every published data version is readable"
    },
    {
      "check": "mixed_kind_names",
      "passed": true,
      "message": "No data name holds versions of both kinds"
    },
    {
      "check": "catalog_completeness",
      "passed": true,
      "message": "Catalog index covers all 7 record(s) on disk"
    }
  ],
  "vaultMismatchFindings": [],
  "contaminationFinding": null,
  "unpushedFilesFinding": null,
  "catalogCompletenessFinding": null,
  "unreadableVersionsFinding": null,
  "mixedKindNamesFinding": null,
  "retentionFinding": null,
  "v3ContentFinding": null,
  "v3KeptLineagesFinding": null,
  "v3ReplicaRestorableFinding": null
}

overallStatus is "pass" when all checks pass and no contamination is detected, "fail" otherwise.

datastoreType identifies the configured backend (e.g. "filesystem", "s3").

isCustom is true for remote/external backends, false for the default filesystem backend.

Each entry in healthFindings includes:

  • check — check identifier
  • passed — true or false
  • message — human-readable result

vaultMismatchFindings contains entries when the vault configuration is incompatible with the datastore backend.

v3ContentFinding is null when the datastore is not v3 or the v3 content check passes.

mixedKindNamesFinding is null when no data name holds versions of both kinds. Otherwise it lists each such name:

{
  "mixedKindNamesFinding": {
    "mixedNames": 1,
    "names": [
      {
        "modelType": "@test/spec",
        "modelId": "m-1",
        "modelName": "spec",
        "dataName": "x",
        "versions": [
          { "version": 1, "kind": "file", "specName": "blob" },
          { "version": 2, "kind": "resource", "specName": "meta" }
        ]
      }
    ]
  }
}
  • mixedNames — the number of data names holding both kinds
  • names — each name's behavior (modelType, modelId, modelName), its dataName, and each live version with its version, kind (file or resource) and specName

A data name holding both kinds is missing its older kind from swamp data list and dataType queries; that kind is readable only by version number. A delete or rename ends a history, so versions before it are not counted. The log output lists the first 20 names. --repair has no stage for this check: read any hidden version with swamp data get <behavior> <name> --version <n>, then delete the name with swamp data delete before writing it again. New writes cannot mix kinds; see One Kind per Data Name.

contaminationFinding is null when no contamination is detected or the datastore does not support contamination checks (filesystem datastores). When contamination is detected:

{
  "contaminationFinding": {
    "foreignNamespaces": [
      { "namespace": "other-team", "objectCount": 148 },
      { "namespace": "staging", "objectCount": 23 }
    ],
    "totalForeignObjects": 171
  }
}

Each entry in foreignNamespaces includes:

  • namespace — the foreign namespace name
  • objectCount — number of objects from that namespace

Repair (--json --repair)

Prints one JSON document in place of the health report, with a key for each repair stage, in the order the stages run:

{
  "unmigrated": null,
  "catalog": { "status": "catalog_not_needed" },
  "duplicateLatest": { "status": "duplicate_latest_not_needed" },
  "contamination": null
}

Each value is that stage's status object, or null when the stage did not run. A stage does not run when the datastore has no namespace, when its namespace has the same name as a datastore layout directory, when the datastore does not support contamination repair (filesystem datastores), or when the stage failed. The exit code and the error on stderr tell these apart. The document is printed even when a stage fails, so the stages that finished are still reported.

Key Status Other fields
unmigrated "unmigrated_preview" namespace, directories
unmigrated "unmigrated_completed" namespace, result
unmigrated "unmigrated_not_needed"
catalog "catalog_preview" completeness, foreignRowsNotice
catalog "catalog_completed" result, foreignRowsNotice
catalog "catalog_not_needed"
duplicateLatest "duplicate_latest_preview" duplicateCount
duplicateLatest "duplicate_latest_completed" result
duplicateLatest "duplicate_latest_not_needed"
contamination "preview" namespace, contamination
contamination "completed" namespace, result, steps
contamination "not_needed"

A *_preview status (or "preview") is printed without -y, a *_completed status (or "completed") with it.

  • directories — unmigrated directories under the namespace that the repair removes.
  • completeness — diskRecords, catalogRecords, and shortfalls, one entry per type with typeNormalized, diskRecords and catalogRecords.
  • foreignRowsNotice — a reminder that rebuilding the catalog index clears rows pulled from other namespaces, and that swamp datastore catalog pull restores them.
  • duplicateCount — catalog rows with a duplicate latest flag.
  • contamination — the same object as contaminationFinding above.
  • steps — the contamination repair steps that ran, each with step, total and description.
  • result — what the stage changed:
Stage result fields
unmigrated removedDirectories, removedFiles
catalog shortfalls, missingRecords
duplicateLatest demotedRows
contamination foreignNamespaces, deletedObjects, filesPulled, workflowRunIndexesInvalidated, catalogInvalidated

The repair for a v3 datastore whose format marker is lost prints a single status object instead; see Format marker repair.

Retention

--retention scans workflow runs, snapshots, outputs, data versions and audit logs for items older than the .swamp.yaml garbageCollection retention, and adds a retention check that fails when any are found. With automatic on, an item counts only once it is two hours past its cutoff. bookkeeping is listed but not scanned.

✓ No stored item is older than its retention

Retention: automatic on
  workflowRuns 30d cutoff 2026-09-16T15:03:34.713Z ✓ none older than cutoff
    last pass 2026-10-16T17:00:59.969Z, cutoff 2026-09-16T17:00:59.969Z, complete
  evaluatedSnapshots 30d cutoff 2026-09-16T15:03:34.713Z ✓ none older than cutoff
    last pass 2026-10-16T17:00:59.969Z, cutoff 2026-09-16T17:00:59.969Z, complete
  outputs 7d cutoff 2026-10-09T15:03:34.713Z ✓ none older than cutoff
    last pass 2026-10-16T17:00:59.969Z, cutoff 2026-10-09T17:00:59.969Z, complete
  data no retention
  audit 90d cutoff 2026-07-18T15:03:34.713Z ✓ none older than cutoff
    last pass 2026-10-16T17:00:59.969Z, cutoff 2026-07-18T17:00:59.969Z, complete
  bookkeeping no retention

last pass is the most recent pass for that kind, with the cutoff it removed items before. It reads incomplete when the pass stopped before finishing the kind.

With --json, the result adds a retentionFinding object:

{
  "retentionFinding": {
    "configured": true,
    "automatic": true,
    "checkedAt": "2026-10-16T17:04:28.007Z",
    "kinds": [
      {
        "kind": "outputs",
        "retention": "7d",
        "cutoff": "2026-10-09T15:04:28.007Z",
        "scanned": true,
        "overdue": 0,
        "items": [],
        "kept": 0,
        "watermark": {
          "lastPassAt": "2026-10-16T17:00:59.969Z",
          "cutoff": "2026-10-09T17:00:59.969Z",
          "complete": true
        }
      }
    ]
  }
}

v3 content check

For a v3 datastore, the v3_content check inspects this checkout's read model and the datastore's chunks. It fails when it finds any of:

  • Unreadable records — records whose content cannot be read because a chunk is missing or damaged in the datastore. Reading one fails with the error code datastore_v3_record_unreadable.
  • Checkout files held aside — checkout files moved into .swamp/_read_model_v3/at-risk/ because the datastore refused their chunks. They hold the only copy this checkout has of that content.
  • Torn chunks — chunks that the checkout's records name, stored at the wrong size.

The check line counts each kind and names the first three records. Below it, each entry is listed, up to ten per kind, with advice for its record kind: for example, write the data again or delete the version with swamp data delete, save the definition again, or delete the at-risk directory to discard the copies held aside.

When the checkout's state cannot be inspected, the check fails with Could not inspect this v3 checkout's content: and the cause.

With --json, the check appears in healthFindings with check: "v3_content", and a failing check sets v3ContentFinding to the full result: unreadableRecords, atRiskCopies and tornChunks counts, unreadable, atRisk and torn lists of the first ten entries (each unreadable and torn entry carries its advice), atRiskDir and atRiskAdvice.

Format marker repair

A v3 datastore records its format in datastore-format.json beside its commits.db. When .swamp.yaml configures a filesystem datastore with format 3 and that marker is missing or blank, every command is refused:

Error: The datastore config in .swamp.yaml says format 3, but the datastore has no readable format marker. It holds a v3 commit log, so a crash may have lost its format marker. Run `swamp doctor datastores --repair` to check the datastore and restore the marker. Nothing was changed.

In that state, --repair checks the datastore's files and previews the restore without changing anything:

$ swamp doctor datastores --repair
Checking the v3 datastore's format marker…

Lost datastore format marker:
  /path/to/v3store/datastore-format.json is missing, beside the v3 commit log of /path/to/v3store

  Run with -y to restore it.

--repair -y writes the marker:

$ swamp doctor datastores --repair -y
Checking the v3 datastore's format marker…

✓ Datastore format marker restored:
  Wrote /path/to/v3store/datastore-format.json (format 3)

  Run `swamp doctor datastores` to check the datastore.

With --json, the result is a single object whose status is format_marker_preview, format_marker_restored, or format_marker_not_needed (the marker was readable by the time the repair checked it):

{
  "status": "format_marker_preview",
  "datastorePath": "/path/to/v3store",
  "markerPath": "/path/to/v3store/datastore-format.json",
  "marker": "absent"
}

marker is "absent" or "blank". A restore that fails exits 1 with the error code format_marker_repair_failed.

On a v3 datastore whose marker is intact, --repair is refused and exits 1:

Error: `swamp doctor datastores --repair` is not available for v3 datastores yet. A v3 checkout rebuilds its records and catalog from the commit log; run `swamp doctor datastores` to check it. Nothing was changed.

Examples

swamp doctor datastores                    # check this repo's datastore
swamp doctor datastores --json             # machine-readable output for CI
swamp doctor datastores --verbose          # show detailed output
swamp doctor datastores --repair           # preview namespace contamination cleanup, or a lost v3 format marker restore
swamp doctor datastores --repair -y        # execute the repair

doctor extensions

Verifies that user-defined extensions in a repository load cleanly and inspects the catalog's aggregate state. Checks each registry (model, which holds behaviors; vault; datastore; report), summarises per-extension source health, detects orphaned catalog rows and bundle files, and optionally repairs the catalog.

Flags

Flag Description
--verbose Show per-source detail for each extension.
--repair Prune Tombstoned catalog rows and evict unreferenced bundle files.
--dry-run Preview repair operations without executing. Use with --repair.
-y, --yes Skip the confirmation prompt. Use with --repair (--force / -f also accepted).

Exit codes

Code Condition
0 All registry checks pass.
1 One or more registry checks failed.

Output

No extensions installed

✓ model
✓ vault
✓ datastore
✓ report

Extension Catalog State
  0 total source(s) across 0 extension(s), 0 healthy (Indexed)


4 passed, 0 failed — OVERALL: PASS

Healthy catalog

✓ model
✓ vault
✓ datastore
✓ report

Extension Catalog State
  1 total source(s) across 1 extension(s), 1 healthy (Indexed)

  @swamp/1password@2026.04.22.2 [pulled]  1 source(s)
    Indexed: 1

4 passed, 0 failed — OVERALL: PASS

Verbose (--verbose)

Adds a Per-Source Detail section listing every source file, its state tag, fingerprint, bundle path, and registry kind.

✓ model
✓ vault
✓ datastore
✓ report

Extension Catalog State
  1 total source(s) across 1 extension(s), 1 healthy (Indexed)

  @swamp/1password@2026.04.22.2 [pulled]  1 source(s)
    Indexed: 1

Per-Source Detail
  vault .swamp/pulled-extensions/@swamp/1password/vaults/onepassword.ts  Indexed fp:3398bcef6091 bundle:.swamp/vault-bundles/a52f5a8b/onepassword.js

4 passed, 0 failed — OVERALL: PASS

Repair dry run (--repair --dry-run)

Previews what --repair would clean up without modifying the catalog.

✓ model
✓ vault
✓ datastore
✓ report

Extension Catalog State
  1 total source(s) across 1 extension(s), 1 healthy (Indexed)

  @swamp/1password@2026.04.22.2 [pulled]  1 source(s)
    Indexed: 1

Repair DRY RUN
  Nothing to clean up — catalog is healthy.

4 passed, 0 failed — OVERALL: PASS

Repair apply (--repair or --repair --yes)

Without --yes, a confirmation prompt is shown before modifying the catalog. With --yes, the prompt is skipped.

RowState values

Each source file in the catalog carries a state tag. The aggregate state section groups sources by these values.

State Meaning
Indexed Source is registered in the catalog and healthy.
Bundled Source has a compiled bundle but is not yet indexed.
BundleBuildFailed Bundle compilation failed. The source is registered but not usable.
ValidationFailed Source failed validation checks.
EntryPointUnreadable The source file's entry point cannot be read from disk.
OrphanedBundleOnly A bundle file exists with no corresponding catalog row.
Tombstoned Source has been removed but the catalog row remains. Pruned by --repair.

JSON shape

Base (--json)

{
  "overallStatus": "pass",
  "registries": {
    "model": { "registry": "model", "status": "pass" },
    "vault": { "registry": "vault", "status": "pass" },
    "datastore": { "registry": "datastore", "status": "pass" },
    "report": { "registry": "report", "status": "pass" }
  },
  "orphanFiles": [],
  "aggregateState": {
    "aggregates": [
      {
        "name": "@swamp/1password",
        "version": "2026.04.22.2",
        "origin": "pulled",
        "sourceCount": 1,
        "stateDistribution": {
          "Indexed": 1,
          "Bundled": 0,
          "BundleBuildFailed": 0,
          "ValidationFailed": 0,
          "EntryPointUnreadable": 0,
          "OrphanedBundleOnly": 0,
          "Tombstoned": 0
        }
      }
    ],
    "sourceDetails": [],
    "catalogOrphans": [],
    "bundleOrphans": [],
    "totalSources": 1,
    "healthySources": 1,
    "orphanRowCount": 0,
    "orphanBundleFileCount": 0
  },
  "warnings": [
    {
      "sourcePath": "extensions/behaviors/foo/bar.ts",
      "category": "TypeExtractionFailed",
      "message": "type field could not be extracted..."
    }
  ]
}

overallStatus is "pass" when all registries pass, "fail" otherwise.

warnings contains advisory diagnostics that do not affect overallStatus or the exit code. Each entry includes:

  • sourcePath — path to the source file that triggered the warning
  • category — warning type. Currently supported:
    • TypeExtractionFailed — the extension's type field could not be extracted from the source file (e.g. the export is dynamically computed or uses an unsupported pattern). The extension still loads and functions normally.
  • message — human-readable description of the issue

The category field is extensible — future swamp versions may add new warning types.

Each registry entry contains registry (the kind name) and status ("pass" or "fail"). The per-registry status is derived from sourceDetails[] — a registry is "fail" when any of its sources carry a failure-state tag (ValidationFailed, BundleBuildFailed, EntryPointUnreadable). sourceDetails[] is the single authoritative failure surface for extension diagnostics.

Each entry in aggregates includes:

  • name — extension name
  • version — installed version
  • origin — "pulled" (from registry) or "local" (user-defined)
  • sourceCount — total source files for this extension
  • stateDistribution — count of sources in each RowState

Verbose (--json --verbose)

sourceDetails is populated with per-source entries:

{
  "sourceDetails": [
    {
      "sourcePath": ".swamp/pulled-extensions/@swamp/1password/vaults/onepassword.ts",
      "stateTag": "Indexed",
      "fingerprint": "3398bcef6091...",
      "bundlePath": ".swamp/vault-bundles/a52f5a8b/onepassword.js",
      "kind": "vault"
    }
  ]
}

Each source detail includes:

  • sourcePath — path to the source file relative to the repository root
  • stateTag — one of the RowState values
  • fingerprint — content hash of the source file
  • bundlePath — path to the compiled bundle (if any)
  • kind — registry kind (model for behaviors, vault, datastore, report)
  • lastError — error message explaining why the source is in a failure state. Present when stateTag is ValidationFailed, BundleBuildFailed, or EntryPointUnreadable; absent for healthy states.

Repair (--json --repair --dry-run)

Adds a repairReport object:

{
  "repairReport": {
    "mode": "dry-run",
    "operations": [],
    "prunedRowCount": 0,
    "evictedFileCount": 0
  }
}

mode is "dry-run" or "applied". Each entry in operations describes a pruned catalog row or evicted bundle file. prunedRowCount and evictedFileCount are totals.

Examples

swamp doctor extensions                    # check this repo's extensions
swamp doctor extensions --json             # machine-readable output for CI
swamp doctor extensions --verbose          # show per-source detail
swamp doctor extensions --repair --dry-run # preview what repair would clean up
swamp doctor extensions --repair           # apply repair (with confirmation)
swamp doctor extensions --repair --yes     # apply repair without prompting

doctor install

Checks Swamp installation health: binary ownership, writability, and autoupdate status. Does not require a Swamp repository — runs from any directory.

Exit codes

Code Condition
0 Installation is healthy.
1 One or more installation checks are unhealthy.

Output

Healthy

Installation Health Check

  Binary path:    /Users/you/.local/bin/swamp
  Version:        20260518.215711.0-sha.d4a64cc1
  Binary owner:   you
  Writable:       ✓ Binary is owned by current user

Autoupdate

  Enabled:        yes
  Cadence:        daily
  Scheduler type: LaunchAgent (user)
  Scheduler:      installed
  Last check:     2026-05-18T22:40:18.898Z (up to date)


HEALTHY

Unhealthy

When a check fails, the status is UNHEALTHY and the failing field shows the error:

Installation Health Check

  Binary path:    /usr/local/bin/swamp
  Version:        20260518.215711.0-sha.d4a64cc1
  Binary owner:   root
  Writable:       ✗ Binary is owned by root, not current user

Autoupdate

  Enabled:        no
  Last check:     2026-05-18T22:04:59.653Z (error)
  Last error:     Cannot update /usr/local/bin/swamp: permission denied. Re-run with: sudo swamp update


UNHEALTHY

Unhealthy conditions

The status is UNHEALTHY when any of these holds:

Condition Applies when autoupdate is
The binary is not writable by the current user. enabled or disabled
The last autoupdate check ended in an error. enabled or disabled
The scheduler is not installed. enabled
The last check is stale (see below). enabled
The scheduler's last run exited with a code other than 0. enabled
macOS launchd refuses to start the job until it is registered again. enabled

A last check is stale when it is older than twice the cadence interval, and never less than 72 hours. The Last check line then ends with — no check for N days, whether or not autoupdate is enabled:

Last check:     2026-05-26T15:14:36.747Z (updated) — no check for 135 days

With autoupdate enabled, a Last exit line follows Scheduler when the scheduler's last run exited with a code other than 0. Code 78 is labelled 78 (EX_CONFIG: the scheduler refused to start swamp).

With autoupdate enabled and the scheduler installed, one warning names the problem, checked in this order:

Problem Warning
launchd refuses to start the job ⚠ macOS is refusing to start the autoupdate job., then It still expects the code signature of the swamp binary that was there before the last update.
The last run exited non-zero and the binary is writable ⚠ The autoupdate job's last run failed (exit N).
The last check is stale ⚠ Autoupdate has stopped checking for updates.

Each warning ends with the repair command:

Re-register it with: `swamp update --setup-auto`

The command is sudo swamp update --setup-auto for a LaunchDaemon, a system systemd timer or a root cron job.

JSON shape

{
  "binaryPath": "/Users/you/.local/bin/swamp",
  "owner": {
    "uid": 501,
    "username": "you",
    "isRoot": false
  },
  "currentVersion": "20260518.215711.0-sha.d4a64cc1",
  "writable": "pass",
  "writableMessage": "Binary is owned by current user",
  "autoupdate": {
    "enabled": true,
    "cadence": "daily",
    "schedulerInstalled": true,
    "schedulerType": "agent",
    "lastEntry": {
      "timestamp": "2026-05-18T22:40:18.898Z",
      "versionBefore": "20260509.235714.0-sha.7ace6b02",
      "versionAfter": "20260518.215711.0-sha.d4a64cc1",
      "outcome": "updated"
    },
    "lastCheckStale": false,
    "lastCheckAgeDays": 0,
    "schedulerLastExitCode": 0,
    "schedulerNeedsRepair": false
  },
  "overallStatus": "healthy"
}

overallStatus is "healthy" when all checks pass, "unhealthy" otherwise.

writable is "pass" when the current user owns the binary, "fail" otherwise.

autoupdate.schedulerType identifies the platform scheduler in use. Present when a scheduler is installed. Possible values:

Value Display label
agent LaunchAgent (user)
daemon LaunchDaemon (system)
systemd-user systemd timer (user)
systemd-system systemd timer (system)
cron-user cron (user)
cron-root cron (root)

autoupdate.lastEntry is present when an autoupdate has been attempted. outcome is one of "updated", "up_to_date", or "error". When outcome is "error", an error field contains the error message.

Field Type Description
autoupdate.lastCheckStale boolean The last check is older than twice the cadence interval, with a 72 hour floor. false when there is no check yet.
autoupdate.lastCheckAgeDays number | null Whole days since the last check, or null when there is none.
autoupdate.schedulerLastExitCode number | null Exit code of the scheduler's last run, or null when it has never exited. Omitted when the scheduler cannot report it.
autoupdate.schedulerNeedsRepair boolean macOS launchd refuses to start the job until it is registered again.

Examples

swamp doctor install        # check installation health
swamp doctor install --json # machine-readable output

doctor secrets

Scans behavior definitions for sensitive global arguments that hold a cleartext literal value instead of a vault.get(...) expression. The scan is read-only and never prints the secret value. It covers models/ (including datastore-synced definitions) and .swamp/auto-definitions. It is the detection complement to the write-time guard that rejects a literal value for a sensitive global argument when a definition is saved.

A global argument is sensitive when the behavior type's globalArguments schema marks it so. See the Behavior Definitions, Vaults, and CEL Expressions references for the schema marking and the vault.get expression syntax.

Flags

Flag Description
-v, --verbose Show detailed output.

Exit codes

Code Condition
0 No cleartext sensitive global arguments found.
1 One or more cleartext sensitive global arguments found.

Output

Clean

Scanning definitions for cleartext sensitive global arguments…

✓ No cleartext sensitive global arguments found (0 definition(s) scanned)

Findings

Each finding lists the definition, the leaked argument path, and value-free remediation steps. The secret value is never shown.

Scanning definitions for cleartext sensitive global arguments…

✗ 1 definition(s) hold a cleartext sensitive global argument (1 definition(s) scanned)

  leaky [@demo/secret-demo]
    • apiKey
        1. Store the secret: swamp vault put my-vault apiKey <value>
        2. Reference it:     ${{ vault.get('my-vault', 'apiKey') }}

  Each secret above sits in cleartext in its definition YAML. Migrate it to a vault, then re-save the definition.

Unresolved

A definition whose extension type is not installed cannot be assessed — its type schema is unavailable, so the scan cannot tell whether any argument is sensitive. These are reported separately as a warning and do not affect overallStatus or the exit code.

Scanning definitions for cleartext sensitive global arguments…

✓ No cleartext sensitive global arguments found (1 definition(s) scanned)

⚠ 1 definition(s) could not be assessed (type schema unavailable — install the extension to scan them)
    • mystery [@demo/uninstalled-type]

JSON shape

{
  "overallStatus": "fail",
  "scanned": 2,
  "findings": [
    {
      "definitionId": "48d8e9d8-aca0-4678-9c07-64b09f4bae55",
      "definitionName": "leaky",
      "type": "@demo/secret-demo",
      "leakedPaths": ["apiKey"],
      "remediations": [
        {
          "path": "apiKey",
          "vaultName": "my-vault",
          "vaultKey": "apiKey",
          "expression": "${{ vault.get('my-vault', 'apiKey') }}"
        }
      ]
    }
  ],
  "unresolved": [
    {
      "definitionId": "faac276d-c74b-4c54-9ca6-b79bb151166f",
      "definitionName": "mystery",
      "type": "@demo/uninstalled-type"
    }
  ]
}

overallStatus is "pass" when findings is empty, "fail" otherwise. unresolved does not affect overallStatus or the exit code.

scanned is the number of definitions inspected.

Each entry in findings includes:

  • definitionId — the definition's id
  • definitionName — the definition's name
  • type — the behavior type
  • leakedPaths — the global-argument paths holding a cleartext sensitive value
  • remediations — one entry per leaked path

Each entry in remediations includes:

  • path — the leaked global-argument path
  • vaultName — the vault to store the secret in
  • vaultKey — the key to store the secret under
  • expression — the vault.get(...) expression to replace the literal with

Each entry in unresolved includes:

  • definitionId — the definition's id
  • definitionName — the definition's name
  • type — the behavior type whose schema is unavailable

Examples

swamp doctor secrets           # scan this repo's definitions
swamp doctor secrets --json    # machine-readable output for CI
swamp doctor secrets --verbose # show detailed output

doctor vaults

Scans behavior definitions for sensitive resource outputs and verifies a vault is configured to store them. A behavior requires a vault when any of its resource specifications has fields marked { sensitive: true } in the schema metadata or the sensitiveOutput: true flag on the specification itself. It also warns when those outputs fall back to the first of several vaults (see Fallback warning).

The scan covers definitions in models/ (including datastore-synced definitions) and .swamp/auto-definitions/.

Flags

Flag Description
-v, --verbose Show detailed output.

Exit codes

Code Condition
0 All behaviors with sensitive outputs have a vault configured.
1 One or more behaviors have sensitive outputs but no vault configured.

Output

Clean

Scanning definitions for sensitive resource outputs without a vault…

✓ All behaviors with sensitive outputs have a vault configured (0 definition(s) scanned)

Findings

Each finding lists the definition name and behavior type. Behaviors in this list will fail at runtime when writing sensitive data.

Scanning definitions for sensitive resource outputs without a vault…

✗ 1 definition(s) have sensitive resource outputs but no vault is configured (2 definition(s) scanned)
    • my-keypair [test/sensitive]

  These behaviors will fail at runtime when writing sensitive data. Create a vault: swamp vault create <type> <name>

Fallback warning

In a repository with two or more user vaults and no default vault in effect — defaultVault unset, or naming a vault that is not registered — the scan warns about every definition whose sensitive outputs name no vault of their own (no field, spec or definition resources vaultName). Those outputs are stored in the first user vault by vault type, then by vault id, which is the order of swamp vault list (see vault resolution order). A workflow step's dataOutputOverrides are not considered.

The warning does not affect overallStatus or the exit code. The pass line ends with , 1 warning:

Scanning definitions for sensitive resource outputs without a vault…

✓ All behaviors with sensitive outputs have a vault configured (1 definition(s) scanned, 1 warning)

⚠ Sensitive outputs without a vault of their own are stored in 'alpha' (defaultVault is not set)
  Without a registered default vault, swamp uses the first of 3 vaults by vault type, then id (the order of 'swamp vault list'): alpha, zeta, default.
    • sm [@demo/secret-maker]

  Name the vault to use: set defaultVault: <vault> in .swamp.yaml.

When defaultVault names a vault that is not registered, the warning says so:

⚠ Sensitive outputs without a vault of their own are stored in 'alpha'
  defaultVault in .swamp.yaml names 'prod-secrets', which is not a vault in this repository.
  Without a registered default vault, swamp uses the first of 3 vaults by vault type, then id (the order of 'swamp vault list'): alpha, zeta, default.
    • sm [@demo/secret-maker]

  Name the vault to use: set defaultVault: <vault> in .swamp.yaml.

Unresolved

A definition whose extension type is not installed cannot be assessed — its type schema is unavailable, so the scan cannot tell whether any resource output is sensitive. These are reported separately as a warning and do not affect overallStatus or the exit code.

Scanning definitions for sensitive resource outputs without a vault…

✓ All behaviors with sensitive outputs have a vault configured (1 definition(s) scanned)

⚠ 1 definition(s) could not be assessed (type schema unavailable — install the extension to scan them)
    • mystery [@demo/uninstalled-type]

JSON shape

{
  "overallStatus": "pass",
  "scanned": 0,
  "hasVault": false,
  "findings": [],
  "unresolved": []
}

overallStatus is "pass" when findings is empty, "fail" otherwise. unresolved does not affect overallStatus or the exit code.

scanned is the number of definitions inspected.

hasVault indicates whether any vault is configured in the repository.

Each entry in findings includes:

  • definitionId — the definition's id
  • definitionName — the definition's name
  • type — the behavior type

Each entry in unresolved includes:

  • definitionId — the definition's id
  • definitionName — the definition's name
  • type — the behavior type whose schema is unavailable

fallbackWarning is present only when the fallback warning applies, and does not affect overallStatus or the exit code:

{
  "fallbackWarning": {
    "fallbackVault": "alpha",
    "userVaults": ["alpha", "zeta", "default"],
    "unregisteredDefaultVault": "prod-secrets",
    "definitions": [
      {
        "definitionId": "facfa23b-0735-4276-9dbb-26b6664c0ba4",
        "definitionName": "sm",
        "type": "@demo/secret-maker"
      }
    ]
  }
}
  • fallbackVault — the vault those sensitive outputs are stored in
  • userVaults — every user vault, in fallback order
  • unregisteredDefaultVault — the defaultVault value, present only when it names a vault that is not registered
  • definitions — the definitions relying on the fallback, each with definitionId, definitionName and type

Examples

swamp doctor vaults        # scan this repo's definitions
swamp doctor vaults --json # machine-readable output for CI

doctor workflows

Checks that workflow YAML files in a repository load cleanly.

Exit codes

Code Condition
0 All workflows load successfully.
1 One or more workflows failed.

Output

No workflows

Checking workflows...
  No workflow files found

0 passed, 0 failed — OVERALL: PASS

Healthy

Checking workflows...
  ✓ hello

1 passed, 0 failed — OVERALL: PASS

Validation failure

When a workflow has schema errors, the validation messages are shown inline:

Checking workflows...
  ✗ broken
    → [
  {
    "expected": "string",
    "code": "invalid_type",
    "path": [
      "id"
    ],
    "message": "Invalid input: expected string, received undefined"
  },
  {
    "expected": "array",
    "code": "invalid_type",
    "path": [
      "jobs"
    ],
    "message": "Invalid input: expected array, received object"
  }
]
  ✓ hello

1 passed, 1 failed — OVERALL: FAIL

JSON shape

Healthy

{
  "overallStatus": "pass",
  "workflows": [
    {
      "file": "workflows/workflow-....yaml",
      "name": "hello",
      "status": "pass"
    }
  ],
  "totalPassed": 1,
  "totalFailed": 0
}

Failed

{
  "overallStatus": "fail",
  "workflows": [
    {
      "file": "workflows/broken.yaml",
      "name": "broken",
      "status": "fail",
      "error": "[\n  {\n    \"expected\": \"string\",\n    ...\n  }\n]"
    },
    {
      "file": "workflows/workflow-....yaml",
      "name": "hello",
      "status": "pass"
    }
  ],
  "totalPassed": 1,
  "totalFailed": 1
}

Each entry in workflows includes:

  • file — path to the workflow YAML file
  • name — workflow name from the YAML
  • status — "pass" or "fail"
  • error — present on failed workflows, contains the validation error

Examples

swamp doctor workflows       # check all workflows
swamp doctor workflows --json # machine-readable output for CI