Releases: systeminit/swamp
swamp 20260302.203007.0-sha.8142ddfc
What's Changed
- feat: show data retrieval commands after workflow run completes (#560)
Summary
- After a successful workflow run, the log output now shows copy-pasteable
swamp data listandswamp data getcommands for each unique data artifact produced - Skipped entirely for failed runs (no useful data) and when no data artifacts exist
- Only affects log mode output (
onWorkflowCompletein the log progress callback); JSON mode is unchanged
Fixes #559
Test plan
- Unit test: helper commands appear for successful runs with data artifacts
- Unit test: no helper commands for failed runs
- Unit test: no helper commands when no data artifacts exist
-
deno check— type checking passes -
deno lint— linting passes -
deno fmt— formatting passes -
deno run test— all 2452 tests pass
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260302.203007.0-sha.8142ddfc/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/v20260302.203007.0-sha.8142ddfc/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/v20260302.203007.0-sha.8142ddfc/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/v20260302.203007.0-sha.8142ddfc/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260302.191852.0-sha.061f496c
What's Changed
- fix: omit empty platforms and labels from extension search JSON output (#557)
Summary
- Omit empty
platformsandlabelsarrays from JSON output ofextension search - Non-empty arrays are still included as before
- Adds tests verifying both empty-omission and non-empty-retention behavior
Test plan
-
deno run test src/presentation/output/extension_search_output_test.tsx— all 14 tests pass - Manual:
swamp extension search --json— verify empty platforms/labels are absent
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260302.191852.0-sha.061f496c/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/v20260302.191852.0-sha.061f496c/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/v20260302.191852.0-sha.061f496c/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/v20260302.191852.0-sha.061f496c/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260302.184040.0-sha.b0814490
What's Changed
- feat: proactive update notification after every command (#556)
Closes #493
Problem
Users currently have no way to know that a new version of swamp is available unless they manually run swamp update --check. This means users can run outdated versions for weeks or months without realizing an update exists.
Solution
Adds a zero-latency background update check that caches results and displays a one-line banner on the next invocation when an update exists. The approach is modeled after Deno's own update notification system.
How it works
- Command runs normally — no latency impact whatsoever
- After the command completes, read the cached notification and display if applicable
- Fire off a background HTTP HEAD check (rate-limited to once per 24h) to the stable artifact URL
- Cache the result in
~/.swamp/last-update-check.jsonfor the next invocation
Two notification types
When a newer version is found in the cache:
$ swamp version
20260206.200442.0-sha.abc123
A new version of swamp is available. Run `swamp update` to upgrade.
When the installed version is >30 days old (zero-network, uses CalVer date):
$ swamp version
20260106.200442.0-sha.abc123
Your swamp version is 55 days old. Run `swamp update` to upgrade.
Guards — notification is suppressed for:
- Dev builds — developers running from source don't need update prompts
SWAMP_NO_UPDATE_CHECK=1— environment variable for CI/scripting contexts--jsonmode — never corrupt structured outputupdatecommand itself — avoid redundant messaging
Architecture
Follows the existing DDD layered architecture:
| Layer | File | Purpose |
|---|---|---|
| Domain | version_staleness.ts |
Pure functions for CalVer date extraction and staleness detection |
| Domain | update_check_cache.ts |
Cache data type, repository port, and isCacheStale() |
| Domain | update_notification_service.ts |
Core service: getNotification() (local I/O only) + backgroundCheck() (fire-and-forget) |
| Infrastructure | update_check_cache_file_repository.ts |
File-based cache at ~/.swamp/last-update-check.json with atomic writes |
| Presentation | update_notification_output.ts |
Renders yellow banner to stderr |
| CLI | mod.ts |
Post-command hook + isUpdateCheckDisabledByEnv() |
Test plan
-
deno check— type checking passes -
deno lint— linting passes -
deno fmt— formatting passes -
deno run test— all 2428 tests pass (82 new) -
deno run compile— binary compiles - Manual: run any command, verify no notification on first run (no cache yet)
- Manual: run again after cache exists with a different version — verify banner
- Manual:
SWAMP_NO_UPDATE_CHECK=1 swamp version— verify no notification - Manual:
swamp version --json— verify no notification in JSON output
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260302.184040.0-sha.b0814490/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/v20260302.184040.0-sha.b0814490/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/v20260302.184040.0-sha.b0814490/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/v20260302.184040.0-sha.b0814490/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260302.181545.0-sha.842b020c
What's Changed
- feat: add
extension searchcommand with install from detail view (#555)
Summary
Adds a new swamp extension search subcommand and an inline install action from the search detail view.
- New
extension searchcommand — queries the swamp extension registry with support for--namespace,--platform,--label,--sort,--page, and--per-pageoptions. Renders an interactive fuzzy-filter UI (Ink + fzf) in log mode, or structured JSON in--jsonmode. - Install from detail view — when viewing an extension's details, pressing
ipulls the extension directly into the current repo, removing the need for a separateswamp extension pullstep. PressingEnterstill returns the selection without installing.Escgoes back to the list. - Exports
pullExtension/PullContext— the core pull logic inextension_pull.tsis now exported so the search command (and future commands) can reuse it without duplicating code.
Why this approach
The extension search → install flow is the most common user journey: find an extension, then install it. Previously this required two separate commands (swamp extension search to discover, then swamp extension pull @ns/name to install). Adding i as a key command in the detail view makes this a single-step operation while keeping the existing Enter to select behavior unchanged.
The implementation follows the existing patterns in the codebase:
- Discriminated result type (
ExtensionSearchResultwithaction: "select" | "install") — matches theWorkflowActionSelectUIpattern already used elsewhere - Lazy repo resolution — the repo context is only resolved when install is actually requested, so searching works without an initialized repo
- Reuse of
pullExtension— avoids duplicating the pull logic (integrity verification, safety analysis, conflict detection, dependency resolution)
Example commands
# Search all extensions (interactive fuzzy filter)
swamp extension search
# Search with a query
swamp extension search aws
# Filter by namespace and platform
swamp extension search --namespace stack72 --platform aws
# Sort by newest, 10 per page
swamp extension search --sort new --per-page 10
# JSON output mode (for scripting)
swamp extension search aws --json
# Interactive flow:
# 1. Run: swamp extension search
# 2. Type to filter, arrow keys to navigate
# 3. Press Enter to view extension details
# 4. Press "i" to install into current repo
# — or Enter to select, Esc to go backTest plan
-
deno check— type checking passes -
deno lint— no lint errors -
deno fmt— formatting clean -
deno run test— all 15 tests pass (12 UI + 3 command validation) - Manual:
swamp extension search→ select → pressi→ installs into repo - Manual:
swamp extension search→ select → pressEnter→ returns item (no install) - Manual:
swamp extension search→ select → pressiwith no repo → clear error
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260302.181545.0-sha.842b020c/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/v20260302.181545.0-sha.842b020c/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/v20260302.181545.0-sha.842b020c/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/v20260302.181545.0-sha.842b020c/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260302.135703.0-sha.7f1c73cc
What's Changed
- docs: add
model method describeto design/models.md (#553)
Summary
Follow-up to #552 — documents the new model method describe CLI command in the design doc.
- Adds a
### model method describe <model_id_or_name> <method_name>section todesign/models.md - Describes both log and JSON output modes
- Placed before the existing
model method runsection
Test plan
- Documentation-only change, no code affected
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260302.135703.0-sha.7f1c73cc/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/v20260302.135703.0-sha.7f1c73cc/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/v20260302.135703.0-sha.7f1c73cc/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/v20260302.135703.0-sha.7f1c73cc/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260302.135354.0-sha.1329659a
What's Changed
- feat: add
model method describesubcommand (#552)
Summary
Closes #550
- Adds
swamp model method describe <model> <method>subcommand that surfaces method-specific documentation (description, typed arguments, data output specs) from model definitions - Supports both builtin and extension models, in log and JSON output modes
- Reuses existing
toMethodDescribeData()andformatSchemaAttributes()from thetype_describemodule — no new domain logic needed
Why this approach
Issue #550 reports that swamp model method run <model> <method> --help shows generic Cliffy help instead of method-specific argument documentation. All the metadata needed already exists in model definitions (method descriptions, Zod argument schemas with .describe()).
Rather than intercepting --help on the run command (which would mix concerns between execution and introspection), this adds a dedicated describe subcommand following the existing model type describe pattern. This is consistent with the CLI's architecture where inspection commands are separate from execution commands.
What changed
New files
| File | Purpose |
|---|---|
src/cli/commands/model_method_describe.ts |
CLI command — resolves model, validates method, renders output |
src/presentation/output/model_method_describe_output.ts |
Output rendering with ModelMethodDescribeData interface |
src/cli/commands/model_method_describe_test.ts |
Command registration tests (3 tests) |
src/presentation/output/model_method_describe_output_test.ts |
Output rendering tests for log + JSON modes (4 tests) |
Modified files
| File | Change |
|---|---|
src/cli/commands/model_method_run.ts |
Register describe subcommand on modelMethodCommand |
integration/ddd_layer_rules_test.ts |
Bump presentation→infrastructure ratchet 31→32 (new output file follows existing writeOutput pattern) |
Testing
Automated
- 7 new unit tests (all pass)
- Full test suite: 2383 passed, 0 failed
deno check/deno lint/deno fmt— cleandeno run compile— binary compiles
Manual (compiled binary, fresh swamp repo init)
Builtin model (command/shell) — log mode:
$ swamp model method describe my-shell-cmd execute
Model: my-shell-cmd
Type: command/shell
Version: 2026.02.09.1
Method: execute - Execute the shell command and capture stdout, stderr, and exit code
Arguments:
run (string) *required
workingDir (string)
timeout (integer)
env (object)
Data Outputs:
result [resource] - Shell command execution result (exit code, timing, command) (infinite)
log [file] - Shell command output (stdout and stderr) (text/plain, infinite)
Extension model (@test/echo) — JSON mode:
{
"modelName": "my-echo",
"modelType": "@test/echo",
"version": "2026.03.02.1",
"method": {
"name": "run",
"description": "Echo the message with a timestamp",
"arguments": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"prefix": {
"description": "Optional prefix for the message",
"type": "string"
}
},
"additionalProperties": false
},
"dataOutputSpecs": [
{
"specName": "data",
"kind": "resource",
"description": "Echo output",
"lifetime": "infinite",
"garbageCollection": 10
}
]
}
}Error cases:
$ swamp model method describe my-shell-cmd nonexistent
Error: "Unknown method 'nonexistent' for type 'command/shell'. Available methods: execute"
$ swamp model method describe nonexistent-model execute
Error: "Model not found: nonexistent-model"
Help listing:
$ swamp model method --help
Commands:
run <model_id_or_name> <method_name> - Execute a method on a model
describe <model_id_or_name> <method_name> - Describe a method on a model with argument details
history - Model method run history commands
Test plan
-
deno checkpasses -
deno lintpasses -
deno fmtpasses -
deno run test— all tests pass -
deno run compile— binary compiles - Manual: builtin model describe works (log + JSON)
- Manual: extension model describe works (log + JSON)
- Manual: error on invalid method name
- Manual: error on invalid model name
- Manual:
model method --helplistsdescribe
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260302.135354.0-sha.1329659a/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/v20260302.135354.0-sha.1329659a/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/v20260302.135354.0-sha.1329659a/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/v20260302.135354.0-sha.1329659a/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260301.183337.0-sha.a2c77652
What's Changed
- feat: support reading secret values from stdin in vault put (#549)
Summary
Closes #548. vault put now supports reading secret values from stdin, so secrets don't need to appear as CLI arguments (which are visible in shell history and ps output).
The problem
swamp vault put <vault> KEY=secret exposes the secret in:
- Shell history (
~/.zsh_history,~/.bash_history) - Process listings (
ps aux) - Agent context — when an AI agent assists with vault setup, it may prompt the user to paste a token into conversation where it gets logged
The plan
Add stdin piping as an alternative input method using the existing readStdin() utility (already used by model edit and workflow edit), with this resolution order:
- If the argument contains
=, parse asKEY=VALUE— existing behavior, unchanged - If no
=, treat argument as justKEYand read the value from stdin - If no
=AND no stdin data, throw a clear error explaining both formats
This preserves full backward compatibility. No new flags, no new subcommands.
The implementation
src/cli/commands/vault_put.ts
- Extracted
parseKeyValue()and newresolveKeyValue()as exported functions for testability resolveKeyValue(argument, stdinContent)encapsulates the resolution order: inlineKEY=VALUEfirst, stdin fallback second, error third- Stdin values have a single trailing newline stripped (
.replace(/\n$/, "")) since pipes/files typically append one - Key detail:
readStdin()is only called whenparseKeyValue()returnsnull(no=found). This avoids consuming stdin unnecessarily, which would breakpromptConfirmation()for the overwrite check in theKEY=VALUEpath - When stdin was piped and the key already exists, a clear
UserErroris thrown instead of attempting an interactive prompt (which can't work with a consumed stdin stream). The user must pass--forcein this case
src/cli/commands/vault_put_test.ts
- Existing
parseKeyValuetests now import from source instead of duplicating the function - 8 new
resolveKeyValuetests covering: inline precedence over stdin, stdin value used when no=, trailing newline stripping, only-one-newline stripping, no-trailing-newline preservation, error on missing value, error message format
.claude/skills/swamp-vault/SKILL.md
- Added piped input syntax to quick reference table
- Documented piped value examples (env var, file, 1Password CLI)
- Added agent security note: never ask users to paste secrets into conversation
Why this is the right fix
- Reuses existing patterns —
readStdin()and the "check stdin, fallback to existing behavior" pattern are already proven inmodel editandworkflow edit - Fully backward compatible —
KEY=VALUEworks identically, zero breakage - No new flags or subcommands — just a natural extension of the existing argument
- Covers all secure input sources — files, env vars, password managers, other CLIs
- Addresses agent leakage — skill update prevents agents from asking for secrets in conversation
- Handles edge cases correctly — stdin only consumed when needed, overwrite with piped stdin requires
--force
End-to-end testing
Created a fresh swamp repo in /tmp with a local_encryption vault and verified all scenarios with the compiled binary:
| # | Test | Command | Result |
|---|---|---|---|
| 1 | Inline KEY=VALUE (backward compat) |
vault put test-vault API_KEY=inline-secret-123 --json |
Stored successfully |
| 2 | Piped from echo | echo "piped-secret-456" | vault put test-vault PIPED_KEY --json |
Stored successfully |
| 3 | Piped from file | cat secret.txt | vault put test-vault FILE_KEY --json |
Stored successfully |
| 4 | No = and no stdin |
vault put test-vault BARE_KEY --json |
Clear error with both usage formats |
| 5 | Inline overwrite with --force |
vault put test-vault API_KEY=updated -f --json |
Overwritten, overwritten: true |
| 6 | Piped overwrite without --force (log mode) |
echo "new" | vault put test-vault PIPED_KEY |
Clear error: "Use --force (-f) to overwrite when piping from stdin" |
| 7 | Piped overwrite with --force |
echo "forced" | vault put test-vault PIPED_KEY -f --json |
Overwritten successfully |
| 8 | Piped from env var | echo "$MY_SECRET" | vault put test-vault ENV_KEY --json |
Stored successfully |
Then created a workflow using ${{ vault.get(test-vault, KEY) }} expressions to read all 4 secrets back. All steps succeeded with values correctly resolved (redacted as *** in persisted data, confirming the secret redactor recognized them as real vault values).
Test plan
-
deno check— type checking passes -
deno lint— linting passes -
deno fmt— formatting passes -
deno run test— 2376 tests pass (16 vault_put tests including 8 new) -
deno run compile— binary compiles - End-to-end manual test with compiled binary in fresh repo
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260301.183337.0-sha.a2c77652/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/v20260301.183337.0-sha.a2c77652/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/v20260301.183337.0-sha.a2c77652/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/v20260301.183337.0-sha.a2c77652/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260228.015056.0-sha.cd6b983d
What's Changed
- feat: add allowFailure flag on workflow steps (#544)
Summary
Closes #541
- Adds
allowFailure: boolean(defaultfalse) to the step schema, allowing steps to fail without propagating failure to the job or workflow - When a step with
allowFailure: truefails, it is recorded as failed withallowedFailure: trueon the StepRun, but does not cause the job to fail - Trigger condition behavior is unchanged:
succeeded→ false,failed→ true,completed→ true — so downstream steps usingdependsOn: completedstill fire - Output rendering shows a
⚠icon for allowed-failure steps instead of the regular✗
Design
The implementation leverages the existing Promise.allSettled pattern in the execution loop. When a step with allowFailure: true catches an error, it marks the StepRun as an allowed failure and returns normally (instead of rethrowing). This means Promise.allSettled sees a fulfilled promise, and jobFailed is never set — so no changes were needed to the job execution loop itself.
Changes
src/domain/workflows/step.ts— AddedallowFailureto schema, class, and serializationsrc/domain/workflows/workflow_run.ts— AddedallowedFailuretracking onStepRunsrc/domain/workflows/execution_service.ts— Modified catch blocks in bothexecuteStep()andexecuteExpandedStep()to handleallowFailuresrc/domain/workflows/job.ts/workflow.ts— AddedInputtypes for backward-compatiblefromData()src/presentation/output/workflow_run_output.ts— Added⚠icon for allowed-failure stepssrc/cli/commands/workflow_run.ts,workflow_history_get.ts,workflow_run_search.ts,workflow_history_search.ts— ThreadallowedFailurethrough to outputdesign/workflow.md— Documented behavior.claude/skills/swamp-workflow/SKILL.md— Added usage example
Test plan
- Unit tests for
Step—allowFailuredefaults to false, round-trips through create/fromData/toData - Unit tests for
StepRun—markAllowedFailure()sets flag, serializes only when true - Execution service tests:
- Step with
allowFailure: truefails → job still succeeds - Step with
allowFailure: truefails → workflow still succeeds - Downstream step with
dependsOn: succeededskips whenallowFailurestep fails - Downstream step with
dependsOn: completedruns whenallowFailurestep fails - Mix of
allowFailureand regular failing steps → job fails (regular failure dominates)
- Step with
- All 2366 tests pass
-
deno check,deno lint,deno fmtpass - Binary compiled and manually tested end-to-end (see PR comment for example workflow)
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260228.015056.0-sha.cd6b983d/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/v20260228.015056.0-sha.cd6b983d/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/v20260228.015056.0-sha.cd6b983d/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/v20260228.015056.0-sha.cd6b983d/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260228.012758.0-sha.ab0f9681
What's Changed
- fix: evaluate workflow expressions at runtime and forward task.inputs in --last-evaluated (#543)
Summary
Fixes #537. Two bugs in workflow run caused ${{ inputs.* }} expressions and task.inputs to be silently dropped during execution.
Bug A — evaluated workflow not used for execution
evaluateWorkflow() returned a correctly evaluated workflow, but the result was stored in a local const evaluatedWorkflow variable that was only saved to cache. The original workflow variable — still containing raw ${{ }} expression strings — was used for the rest of execution. This meant modelIdOrName arrived at the step executor as the literal string ${{ inputs.deviceModel }} instead of the resolved model name.
Fix: Change const evaluatedWorkflow = ... to workflow = ... so the evaluated workflow is actually used for execution.
Bug B — --last-evaluated drops task.inputs
In executeModelMethod(), the task.inputs evaluation and setMethodArgument() forwarding was inside the else if (ctx.expressionContext) branch (the normal expression evaluation path). When useLastEvaluated was true, expressionContext was forced to undefined by a ternary (lastEvaluated ? undefined : expressionContext), so the entire block was skipped and model methods received undefined for all input fields.
Fix:
- Hoist
stepInputsdeclaration above the if/else - In the
useLastEvaluatedbranch, setstepInputs = task.inputs(values are already resolved in the pre-evaluated workflow) - Move the
setMethodArgumentforwarding loop outside the if/else so it runs for both paths
Minimal expression context for --last-evaluated
To support step-output-dependent expressions (e.g. ${{ model.previous.resource.foo.bar }}) that were deferred during workflow evaluate:
- Build a minimal expression context (
{ model: {}, env: buildEnvContext() }) even whenlastEvaluatedis true - Remove the
lastEvaluated ? undefined : expressionContextternaries at bothexecuteStepandexecuteExpandedStep— the expression context is now always passed through
This allows step outputs to be tracked in the context during execution, enabling deferred expressions to be evaluated at step execution time.
Test plan
Unit tests (2 new regression tests)
- Bug A test —
workflow expressions are evaluated before step execution: creates a workflow with${{ inputs.deviceModel }}as the model name, verifies the step executor receives"my-device"(not the raw expression string) - Bug B test —
useLastEvaluated context carries task.inputs and expressionContext: creates a workflow with task.inputs, saves it as an evaluated workflow, runs withlastEvaluated: true, verifies the expression context is provided and task.inputs are present - All 2348 existing tests continue to pass
End-to-end manual testing
Created a fresh swamp repo in /tmp with a command/shell model (my-echo) and two workflows:
Bug A verification — workflow with modelIdOrName: ${{ inputs.modelName }}:
swamp workflow run test-bug-a --input '{"modelName":"my-echo"}' --json
Result: succeeded — ${{ inputs.modelName }} resolved to my-echo, model found and executed.
Bug B verification — workflow with both expression in model name and task.inputs:
swamp workflow evaluate test-bug-b --input '{"modelName":"my-echo","message":"echo hello-from-bug-b-test"}' --json
swamp workflow run test-bug-b --last-evaluated --json
Result: succeeded — evaluated YAML shows resolved values, --last-evaluated correctly forwarded task.inputs.run as "echo hello-from-bug-b-test", stdout confirmed hello-from-bug-b-test.
Combined verification — direct workflow run with both expressions (no evaluate step needed):
swamp workflow run test-bug-b --input '{"modelName":"my-echo","message":"echo direct-run-works"}' --json
Result: succeeded — both modelIdOrName and task.inputs.run resolved correctly, stdout confirmed direct-run-works.
Verification checklist
-
deno check— type checking passes -
deno lint— linting passes -
deno fmt— formatting passes -
deno run test— 2348 tests pass -
deno run compile— binary compiles successfully
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260228.012758.0-sha.ab0f9681/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/v20260228.012758.0-sha.ab0f9681/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/v20260228.012758.0-sha.ab0f9681/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/v20260228.012758.0-sha.ab0f9681/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260228.011207.0-sha.151c2054
What's Changed
- feat: persist evaluated definitions in model evaluate (#542)
Summary
Fixes #538 — swamp model evaluate resolves CEL expressions correctly but never persisted evaluated definitions to .swamp/definitions-evaluated/. This was inconsistent with swamp workflow evaluate (which persists to .swamp/workflows-evaluated/), and meant --last-evaluated flags on downstream commands couldn't find the evaluated definitions.
What changed
src/cli/commands/model_evaluate.ts: AddedevaluatedDefinitionRepo.save()calls in both the single-model and--allcode paths, following the exact pattern already used bymodel method run(lines 240-242 ofmodel_method_run.ts). Also populatedoutputPathin the output data so JSON consumers can locate the persisted file.
Secret safety
No additional redaction logic is needed. The evaluation service uses a two-phase model:
- Persist phase (what
model evaluatedoes): Only CEL expressions are resolved. Vault expressions (vault.get(...)) and env expressions (env.*) are detected bycontainsVaultExpression()/containsEnvExpression()and left as raw${{ ... }}strings. - Runtime phase (only during method execution):
resolveRuntimeExpressionsInDefinition()resolves vault/env expressions in memory, registers secrets withSecretRedactor, and never persists them.
Since model evaluate only calls the persist-phase evaluation, the persisted YAML files will never contain actual secret values.
Testing
Integration tests (integration/model_evaluate_test.ts)
Three new integration tests verify the fix:
-
Single model persistence — Creates a model with CEL expressions (
${{ 1 + 1 }}), runsmodel evaluate, verifies:- File written to
.swamp/definitions-evaluated/command/shell/<id>.yaml - CEL expression evaluated to
2in persisted file outputPathpresent in JSON output pointing todefinitions-evaluated/
- File written to
-
--allpersistence — Creates 3 models, runsmodel evaluate --all, verifies:- All 3 files exist in
.swamp/definitions-evaluated/command/shell/ - All items in JSON output have
outputPathset
- All 3 files exist in
-
Vault expression preservation — Creates a model with
${{ vault.get('my-vault', 'api-key') }}, runsmodel evaluate, verifies:- Persisted file still contains
vault.getas a raw string (not resolved)
- Persisted file still contains
Manual end-to-end verification
Created a fresh swamp repo in /tmp, added models with CEL and vault expressions, and verified:
$ swamp model evaluate my-server --json
{
"id": "f57604ae-...",
"name": "my-server",
"type": "command/shell",
"hadExpressions": true,
"outputPath": "/tmp/swamp-evaluate-test/.swamp/definitions-evaluated/command/shell/f57604ae-....yaml",
"globalArguments": {
"computed_value": 2, # was "${{ 1 + 1 }}"
"static_value": "hello"
}
}
Persisted evaluated definition:
globalArguments:
computed_value: 2 # CEL evaluated
static_value: hello
methods:
execute:
arguments:
run: echo deploying version 6 # was "${{ 2 * 3 }}"Vault expressions preserved as raw strings:
globalArguments:
api_key: '${{ vault.get(''my-vault'', ''api-key'') }}' # NOT resolved
env_var: '${{ env.HOME }}' # NOT resolvedCI verification
deno check— passeddeno lint— passeddeno fmt— passeddeno run test— all 2349 tests pass, 0 failuresdeno run compile— binary compiled successfully
Test plan
- Integration test: single model persists to
definitions-evaluated/ - Integration test:
--allpersists all models - Integration test: vault expressions preserved as raw strings
- Manual:
swamp model evaluate <name> --jsonproducesoutputPath - Manual:
swamp model evaluate --all --jsonproducesoutputPathfor all items - Manual: persisted files have evaluated CEL, raw vault/env expressions
- Full test suite passes (2349 tests)
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260228.011207.0-sha.151c2054/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/v20260228.011207.0-sha.151c2054/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/v20260228.011207.0-sha.151c2054/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/v20260228.011207.0-sha.151c2054/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/