Releases: systeminit/swamp
swamp 20260311.210849.0-sha.1db81d92
What's Changed
- feat: add data rename command with forward-reference resolution (#689)
Closes #621
Summary
Adds swamp data rename <model> <old_name> <new_name> — a non-destructive rename command that preserves version history and maintains backwards compatibility through forward-reference resolution.
Why this is useful
Data instance names in swamp are permanent once created. Before this change, renaming a data instance (e.g., web-vpc → dev-web-vpc) required deleting the old data and recreating it under the new name. This is destructive: you lose all version history, and any workflows, CEL expressions, or model inputs referencing the old name break immediately.
Scenarios where rename is needed
- Refactoring naming conventions — As a project grows, early naming choices (
vpc,subnet) need to become more specific (dev-web-vpc,prod-private-subnet) to avoid ambiguity - Reorganizing after a model's purpose evolves — A model initially managing one resource expands scope, and its data names no longer reflect what they contain
- Fixing typos — A misspelled data name (
deplyoment-state) can be corrected without losing history or breaking consumers - Multi-environment setups — When splitting a single model into per-environment variants, existing data needs environment-prefixed names
How it works
The implementation uses a Copy + Tombstone with Forward Reference strategy:
- Copy: The latest version of the old data is copied to the new name as version 1
- Tombstone: A new version is written on the old name with
lifecycle: "deleted"and arenamedTometadata field pointing to the new name - Forward resolution: When any consumer looks up the old name (via
findByName, CEL expressions likedata.latest(), ormodel.*expressions), the repository layer detects the tombstone and transparently follows the forward reference to the new name - History preserved: Version-specific lookups (
data.version("model", "old-name", 2)) bypass forwarding and return the original historical data
User impact
- Zero-downtime rename: Existing workflows and expressions referencing the old name continue to work immediately after rename — no manual updates required for things to keep running
- Gradual migration: Users can update references at their own pace; the forward reference handles the transition
- Full audit trail: The old name's version history is preserved; the tombstone version clearly marks when the rename occurred
- CEL expression compatibility:
data.latest(),data.version(),data.findByTag(), andmodel.*resource expressions all work transparently
Design decisions
- Forward reference depth limit of 5: Prevents infinite loops from chained renames (A→B→C→D→E→F stops resolving)
- Repository-level resolution: Forwarding happens in
UnifiedDataRepository.findByName(), so every consumer benefits without code changes - Deduplication in list operations: After rename, directory scans would return both old (forwarded) and new names;
findAllForModeldeduplicates by resolved name using aSet - Tombstone skipping in expressions: The model resolver's eager population loop skips renamed tombstones to avoid polluting expression contexts
- Warning on rename: Users are warned that re-running a model that writes to the old name will overwrite the forward reference
What changed
New files
src/cli/commands/data_rename.ts— CLI command definitionsrc/cli/commands/data_rename_test.ts— CLI command unit testssrc/domain/data/data_rename_service.ts— Domain service orchestrating the renamesrc/presentation/output/data_rename_output.ts— Output rendering (logger for log mode, JSON for json mode)
Modified files
src/domain/data/data.ts— AddedrenamedTofield,isRenamedgetter,withRenameMarker()factory methodsrc/domain/data/data_metadata.ts— AddedrenamedToto Zod schemasrc/infrastructure/persistence/unified_data_repository.ts— Addedrename()method, forward-reference following infindByName/findByNameSync, deduplication infindAllForModel/findAllForModelSyncsrc/cli/commands/data.ts— Registeredrenamesubcommandsrc/cli/commands/data_get.ts— Fixed content path to use resolved name after forwardingsrc/domain/expressions/model_resolver.ts— Skip renamed tombstones in eager populationintegration/ddd_layer_rules_test.ts— Bumped ratchet counts for new service/output files- 5 test mock files — Added
renamestub to mockUnifiedDataRepositoryimplementations
Skill documentation
.claude/skills/swamp-data/SKILL.md— Added rename to quick reference, new "Rename Data" section.claude/skills/swamp-data/references/examples.md— Added rename scenarios.claude/skills/swamp-data/references/troubleshooting.md— Added rename troubleshooting, removed defunct index/symlink section
Test plan
Unit tests added (in data_test.ts)
-
withRenameMarkercreates correct tombstone withlifecycle: "deleted"andrenamedTo -
isRenamedreturns true only for deleted data withrenamedToset -
isRenamedreturns false for deleted data withoutrenamedTo -
isRenamedreturns false for active data withrenamedTo(edge case) -
toData/fromDataroundtrip preservesrenamedTo -
toDataomitsrenamedTowhen not set -
withNewVersionpreservesrenamedTofield
CLI tests added (in data_rename_test.ts)
- Module loads without error
- Command has correct description
- Command is registered as
renamesubcommand ofdata -
--repo-diroption is available - Command requires three positional arguments
E2E testing performed manually
-
swamp data renamecopies data to new name correctly - Forward reference resolves:
swamp data get model old-namereturns new name's data -
swamp data listshows renamed data only once (no duplicates) - Historical version access:
swamp data get model old-name --version 1returns original -
data.latest()in CEL expressions resolves through forward reference - Workflow re-run after rename produces data under new name correctly
Verification
-
deno fmt— passed -
deno lint— passed -
deno check— passed -
deno run test— 2835 passed, 0 failed -
deno run compile— successful
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.210849.0-sha.1db81d92/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/macOS (Intel):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.210849.0-sha.1db81d92/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (x86_64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.210849.0-sha.1db81d92/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (aarch64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.210849.0-sha.1db81d92/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260311.190346.0-sha.980e703f
What's Changed
- ci: add skill review CI step (#688)
Summary
- Add
scripts/review_skills.ts— reviews all bundled skills vianpx tessl skill review, builds a markdown summary table forGITHUB_STEP_SUMMARY, and exits 1 if any skill's average score drops below 90% - Add
review-skillstask todeno.json - Add
skill-reviewCI job (Deno + Node.js) to.github/workflows/ci.yml - Update
needsarrays forclaude-review,claude-adversarial-review, andauto-mergeto gate onskill-review - Update skill descriptions and add
data-ownership.mdandexpressions.mdreference docs toswamp-dataandswamp-modelskills - Register new reference files in
skill_assets.tsand update tests
Test plan
- Ran
deno run review-skillslocally — all 8 skills pass at 100% - CI
skill-reviewjob passes on this PR - Existing
test,deps-audit, and review jobs still pass
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.190346.0-sha.980e703f/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/macOS (Intel):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.190346.0-sha.980e703f/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (x86_64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.190346.0-sha.980e703f/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (aarch64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.190346.0-sha.980e703f/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260311.170115.0-sha.0c667d10
What's Changed
- docs: remove logical views and fix .swamp/ references in design docs (#687)
docs: remove logical views and fix .swamp/ references in design docs
Summary
The datastore refactor moved source-of-truth files from .swamp/ to top-level models/, workflows/, vaults/ directories and replaced symlink-based "logical views" with a NoopRepoIndexService. The design docs were stale — they still referenced logical views and .swamp/ as the canonical storage for definitions/workflows/vaults.
This PR updates all 9 design docs to reflect the current architecture:
- high-level.md: Replace
.swamp/aggregate storage + logical views with top-level source-of-truth dirs + datastore - repo.md: Remove
.swamp/internal storage and logical view sections; documentNoopRepoIndexService; replace symlink directory structure with real file layout - agent.md: Replace logical views +
.swamp/direct access with source-of-truth directories + datastore + CLI abstraction - models.md: Fix definition paths (
models/), data paths (datastore), output paths (datastore); remove logical views section - workflow.md: Fix workflow paths (
workflows/), run paths (datastore), data paths (datastore); remove logical views section - vaults.md: Fix vault config path (
vaults/{type}/{id}.yaml); remove logical views; update secret storage to note datastore paths - expressions.md: Add datastore path notes for
data/and evaluated directories - extension.md: Note
.swamp/bundles/as a datastore path - datastores.md: Emphasize source-of-truth files are always top-level
Test plan
- All paths verified against
src/infrastructure/persistence/paths.ts,src/cli/repo_context.ts, and repository implementations - No remaining stale references to
/.swamp/definitions/,/.swamp/workflows/, or/.swamp/vault/ - Remaining "symlink" references are appropriate (latest data symlinks, extension safety checks, noop explanation)
- Docs-only change — no code changes, no tests/compilation needed
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.170115.0-sha.0c667d10/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/macOS (Intel):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.170115.0-sha.0c667d10/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (x86_64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.170115.0-sha.0c667d10/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (aarch64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.170115.0-sha.0c667d10/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260311.165546.0-sha.5d8b2333
What's Changed
- fix: prevent 1Password CLI from reading exhausted stdin pipe (#686)
Summary
Fixes #668 — swamp vault put to a 1Password-backed vault stores an empty password field when the secret value is piped via stdin.
Root cause: The runOp() helper in OnePasswordVaultProvider spawned op subprocesses without setting stdin: "null", so they inherited the parent process's stdin. When stdin was a pipe (already consumed by readStdin()), the op CLI detected the pipe and attempted to read from it, getting an empty string that overwrote the field=value command-line argument.
Fix: Add stdin: "null" to both Deno.Command calls in the 1Password vault provider (runOp and checkOpInstalled) so child processes never inherit the parent's stdin. No other vault providers are affected — only the 1Password provider shells out to a CLI tool.
The inline KEY=VALUE syntax was unaffected because stdin remains a TTY in that path, so op never tries to read from it.
Test Plan
- Existing unit tests pass (
deno run test) deno check,deno lint,deno fmtall clean- The fix can be verified manually with:
echo "test-value" | swamp vault put <1p-vault> TEST_KEY
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.165546.0-sha.5d8b2333/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/macOS (Intel):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.165546.0-sha.5d8b2333/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (x86_64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.165546.0-sha.5d8b2333/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (aarch64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.165546.0-sha.5d8b2333/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260311.162257.0-sha.7f330d44
What's Changed
- fix: handle symlinks and YAML type field in definition repository (#685)
Summary
Fixes #683 — after the datastore refactor, model definitions using symlinks or scoped extension types (e.g., @smith/kibana-dev) were no longer discovered.
Two bugs in yaml_definition_repository.ts:
-
Symlinks silently skipped:
Deno.readDir()returnsisFile: falsefor symlinks, sofindAll(),findByNameGlobal(), andfindAllGlobal()all missed symlinked YAML files. Fixed by also checkingentry.isSymlink, withassertSafePath()guarding against symlinks that escape the repo boundary. -
Type reconstructed from path instead of YAML:
collectAllDefinitions()andsearchDefinitionByName()reconstructed the model type from directory path segments, ignoring thetypefield in the parsed YAML. This produced wrong types for scoped extensions where the directory name doesn't match the full type (e.g., path sayskibana-devbut YAML says@smith/kibana-dev). Fixed by preferringdefinition.typefrom the YAML, falling back to path-based type only when absent.
Important: The path traversal boundary uses repoDir (not baseDir/models/) so that symlinks targeting extensions/models/ or .swamp/definitions/ — inside the repo but outside models/ — are correctly allowed while symlinks escaping the repo are still rejected.
User impact
Users with extension models installed via symlinks (or legacy symlink layouts from before repo upgrade) will have their models discovered again. Users with scoped extension types will see the correct type reported instead of a truncated path-based type.
Test plan
- Symlinked YAML file discovered by
findAll()(target outsidemodels/, in.swamp/definitions/) - Symlinked YAML file discovered by
findAllGlobal()(target inextensions/models/) - Symlinked YAML file discovered by
findByNameGlobal()(target inextensions/models/) - Combined scenario: symlinked extension with scoped type
@smith/kibana-dev— both symlink discovery and type resolution work together - Type read from YAML
typefield, not path, when present - Path-based type used as fallback when YAML
typefield is missing - Symlink pointing outside repo boundary is rejected (path traversal protection)
- All 2823 existing tests pass
-
deno check,deno lint,deno fmt,deno run compilepass
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.162257.0-sha.7f330d44/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/macOS (Intel):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.162257.0-sha.7f330d44/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (x86_64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.162257.0-sha.7f330d44/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (aarch64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.162257.0-sha.7f330d44/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260311.151803.0-sha.f73d7a6d
What's Changed
- fix: respect --repo-dir for user model and vault loading (#684)
Summary
Fixes #682.
loadUserModels(), loadUserVaults(), and initTelemetryService() in src/cli/mod.ts hardcoded Deno.cwd() to locate the swamp repo at CLI startup — before Cliffy parses command options. This meant --repo-dir was completely ignored for:
- Extension model type registration → "Unknown model type" when running from a different directory
- User vault implementation loading
- Telemetry config and log level resolution from
.swamp.yaml
Approach
Pre-parse --repo-dir from the raw args array (following the existing getOutputModeFromArgs pattern for --json) and thread the resolved path through all four startup functions. This is the minimal fix — no command-level changes, no changes to requireInitializedRepo, and behavior is identical when --repo-dir is not specified.
Changes
src/cli/context.ts— AddedgetRepoDirFromArgs(args)that extracts--repo-dirfrom raw CLI args (supports both--repo-dir <value>and--repo-dir=<value>forms), resolves to an absolute path, defaults to cwdsrc/cli/mod.ts— CallgetRepoDirFromArgs(args)once at the top ofrunCli(), pass the result toloadUserModels(repoDir),loadUserVaults(repoDir),initTelemetryService(repoDir), and the marker read blocksrc/cli/context_test.ts— 6 new tests for the pre-parser (no flag, space separator, equals separator, relative path resolution, missing value, flag among other args)
Test Plan
- 6 new unit tests for
getRepoDirFromArgscovering all arg forms and edge cases - Full test suite passes (2811 tests, 0 failures)
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.151803.0-sha.f73d7a6d/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/macOS (Intel):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.151803.0-sha.f73d7a6d/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (x86_64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.151803.0-sha.f73d7a6d/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (aarch64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.151803.0-sha.f73d7a6d/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260311.151523.0-sha.5c71d81f
What's Changed
- fix: reject path traversal in workflow names (#681)
Summary
WorkflowSchema.namewas missing the path-traversal validation thatDefinitionSchemaalready has —swamp workflow create "../../etc/passwd"silently succeeded (exit 0) instead of failing- Added
.refine()guard rejecting..,/,\, and null bytes (matching theDefinitionSchemapattern) - Added eager name validation in
Workflow.create()so path traversal is caught even when the full schema parse is skipped (empty-jobs code path) - 4 new unit tests covering all rejection cases
Test plan
- 26 workflow unit tests pass (including 4 new path traversal tests)
-
deno checkpasses -
deno lintpasses -
deno fmt --checkpasses
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.151523.0-sha.5c71d81f/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/macOS (Intel):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.151523.0-sha.5c71d81f/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (x86_64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.151523.0-sha.5c71d81f/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (aarch64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.151523.0-sha.5c71d81f/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260311.135022.0-sha.55361247
What's Changed
Summary
Follow-up to #678 (datastore lock deadlock fix) and #675 (non-TTY interactive command fix). Addresses three issues identified in adversarial review of those PRs:
-
TOCTOU race in lock release (
datastore_lock.ts): AddedforceRelease(expectedNonce)to theDistributedLockinterface so each backend (S3Lock, FileLock) can verify the nonce and delete in the tightest possible sequence. Thedatastore lock releasecommand now delegates to this method instead of branching on config type with separate delete logic. This also removed the redundantS3Clientinstantiation andjoinimport from the command handler. -
Double marker file read in
requireInitializedRepo(repo_context.ts): The refactoring in #678 causedrequireInitializedRepoto read the.swamp.yamlmarker file twice — once insideresolveDatastoreForRepoand again directly.resolveDatastoreForReponow returns the marker it already read, andrequireInitializedReporeuses it, eliminating the redundant I/O. -
extension_search.tsmissed in #675: PR #675 updated 10 interactive search commands to useinteractiveOutputMode()but missedextension_search. Applied the same pattern so it won't crash with "Raw mode is not supported" in non-TTY contexts (e.g.echo "" | swamp extension search aws).
Test plan
- 6 new tests for
forceRelease(3 per lock backend): matching nonce, mismatched nonce, no lock exists - Updated
resolveDatastoreForRepotest to verify marker is returned - All 2805 tests pass
-
deno checkpasses -
deno lintpasses -
deno fmt --checkpasses
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.135022.0-sha.55361247/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/macOS (Intel):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.135022.0-sha.55361247/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (x86_64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.135022.0-sha.55361247/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (aarch64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.135022.0-sha.55361247/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260311.132338.0-sha.398857eb
What's Changed
- fix: prevent datastore lock commands from deadlocking on stuck lock (#678)
Fixes #676
Summary
datastore lock statusanddatastore lock release --forcecalledrequireInitializedRepo()which acquires the datastore lock viaregisterDatastoreSync()— causing the breakglass commands to deadlock on the very lock they were meant to inspect or release- Added
resolveDatastoreForRepo()tosrc/cli/repo_context.ts— a lightweight function that resolves repo path + datastore config without acquiring the lock. The lock commands now use this instead - Refactored
requireInitializedRepo()to callresolveDatastoreForRepo()internally, keeping a single source of truth for repo validation and datastore config resolution - Zero user impact for normal usage — only the two broken breakglass commands are affected, and they now work
Test Plan
- New tests for
resolveDatastoreForRepo: returns correct config without acquiring lock, throwsUserErrorfor non-initialized repos - All 2799 tests pass
-
deno fmt --checkpasses -
deno lintpasses -
deno checkpasses - Verified no UAT test impact — no UAT tests exercise
datastore lockcommands
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.132338.0-sha.398857eb/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/macOS (Intel):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.132338.0-sha.398857eb/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (x86_64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.132338.0-sha.398857eb/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (aarch64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.132338.0-sha.398857eb/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260311.105043.0-sha.fa48cb0c
What's Changed
- fix: gracefully handle non-TTY context in interactive commands (#675)
Fixes #674
Summary
-
Interactive search commands (model search, type search, workflow search, etc.) use Ink for terminal UIs which requires raw mode on stdin. In non-TTY contexts (piped input, CI, AI agents), Ink crashes with "Raw mode is not supported". This was a pre-existing bug — it existed before datastores — but the introduction of the datastore lock made it much worse: the crash now leaks the lock, blocking all subsequent swamp commands for ~60 seconds until timeout.
-
Fix 1 —
interactiveOutputMode()helper (src/cli/context.ts): A new function that returns"json"whenctx.outputModeis"json"OR when stdin is not a TTY. Applied surgically to all 10 interactive search commands so they fall back to structured JSON output instead of crashing. Non-interactive commands (version, model get, workflow run, etc.) are unaffected — they keep their normal output mode. -
Fix 2 — Safety net in
main.ts: AddedflushDatastoreSync()to the top-level catch block so the datastore lock is released even on unexpected crashes that bypassrunCli()'s own error handling.flushDatastoreSync()is idempotent (nulls its state on first call), so calling it a second time is harmless.
Commands updated with interactiveOutputMode:
model search,type search,vault search,vault type searchworkflow search,workflow run search,workflow history searchmodel method history search,model output search,data search
Test Plan
- All 2797 tests pass (zero failures)
-
deno fmt --checkpasses -
deno lintpasses -
deno checkpasses - Verified the global outputMode approach (auto-switching all commands) caused 6 test failures — confirmed the surgical per-command approach is correct
- Verified non-interactive commands retain log-mode output in non-TTY contexts
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.105043.0-sha.fa48cb0c/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/macOS (Intel):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.105043.0-sha.fa48cb0c/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (x86_64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.105043.0-sha.fa48cb0c/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/Linux (aarch64):
curl -L https://github.com/systeminit/swamp/releases/download/v20260311.105043.0-sha.fa48cb0c/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/