Releases: systeminit/swamp
swamp 20260506.233640.0-sha.5729ac50
What's Changed
- fix(extensions): reject quality and fmt on pulled extensions (swamp-club#239) (#1328)
Summary
extension qualityandextension fmtfail on pulled extensions because the manifest'spaths.basedefaults totypedDir, resolving model files againstextensions/models/in the repo root instead of the pulled extension's directory- Added
isPulledExtensionManifest()helper that detects manifests under.swamp/pulled-extensions/and rejects both commands early with a clear error message - Pulled extensions are read-only registry copies — running fmt/quality on them is not useful
Test Plan
- Unit tests for
isPulledExtensionManifest(absolute/relative/local/non-.swamp paths) - Reproduced original bug in scratch repo:
extension qualityon pulled manifest → "Model file not found" - Verified fix: both commands now reject with clear error message
- Verified local extensions still work normally
- Full test suite passes (5570 tests, 0 failures)
-
deno check,deno lint,deno fmtall clean
Closes swamp-club#239
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260506.233640.0-sha.5729ac50/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/v20260506.233640.0-sha.5729ac50/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/v20260506.233640.0-sha.5729ac50/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/v20260506.233640.0-sha.5729ac50/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260506.221457.0-sha.15cd7fdc
What's Changed
- fix(extensions): preserve catalog fingerprint when bundle build fails (swamp-club#265) (#1327)
Summary
- When
bundleWithCachereturns a stale cached bundle (viaisExpectedBundleFailurefast-path or error fallback),rebundleAndUpdateCatalognow preserves the catalog's storedsource_fingerprintinstead of writing the new one. This prevents permanent fingerprint poisoning wherefindStaleFilessees matching fingerprints and never retries the bundle. - Adds kind-agnostic
BundleResulttype tobundle_freshness.tswith{ js, fromCache }— shaped for W4'sKindAdapterto absorb when it consolidates the loaders. - Emits a structured warning ("source fingerprint preserved, will retry on next command") only on the fallback case (
fromCache && fingerprint differs), not on legitimate cache hits. - Applied mechanically across all 5 extension loaders (models, drivers, vaults, datastores, reports).
bundleAndIndexOnesurfacesfromCacheas an additive return field — no caller changes needed (reconcile and install services create Sources independently).findStaleFilessignature unchanged (W2'sRemoveExtensionServicesafety-net preserved).- Filed deferred tracker issues: swamp-club#270 (warm-start state oscillation debt) and swamp-club#271 (sourceToRow empty mtime).
Test Plan
- Added warm-start regression integration test (
integration/source_extension_rebundle_test.ts) with three assertion groups:- (a) Happy path: source-mounted extension primed with V1, modified to V2 with bare specifiers
- (b) Permanent-failure determinism: two consecutive warm-starts assert identical catalog fingerprint and warning emitted both times
- (c) Warning-log precision: assert warning fires for fallback case, assert NO warning for unchanged-source warm-start
- Verified before/after against compiled binary vs swamp on PATH:
- Before: fingerprint poisoned (
faa89ac09cda→69ead2141011), bundle stale, no warning, permanently stuck - After: fingerprint preserved (
faa89ac09cda), bundle stale but retryable, warning emitted, deterministic
- Before: fingerprint poisoned (
- All 5562 existing tests pass, 0 failures
- Existing
bundle_cache_freshness_test.ts(issue #125 regression net) passes
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260506.221457.0-sha.15cd7fdc/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/v20260506.221457.0-sha.15cd7fdc/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/v20260506.221457.0-sha.15cd7fdc/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/v20260506.221457.0-sha.15cd7fdc/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260506.195744.0-sha.60b2e6d2
What's Changed
- feat(cli): add swamp issue get command (swamp-club#118) (#1326)
Summary
- Adds
swamp issue get <number>CLI command to fetch and display Lab issue details (title, type, status, author, body, assignees, comment count) - Supports both
log(human-readable) andjson(structured) output modes - Adds
fetchIssuemethod toSwampClubClientusingx-api-keyauth (consistent with existing methods) - Updates
swamp-issueskill with new command documentation and JSON output shape
Closes swamp-club#118
Test Plan
- Unit tests for
issueGetlibswamp generator (src/libswamp/issues/get_test.ts) - CLI command structure test (
src/cli/commands/issue_get_test.ts) -
deno check— all files pass type checking -
deno lint— no lint errors -
deno fmt— all files formatted -
deno run test— 5542 passed (1 pre-existing flaky failure in unrelated workflow test) -
deno run compile— binary compiles successfully
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260506.195744.0-sha.60b2e6d2/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/v20260506.195744.0-sha.60b2e6d2/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/v20260506.195744.0-sha.60b2e6d2/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/v20260506.195744.0-sha.60b2e6d2/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260506.190736.0-sha.f040ad12
What's Changed
- feat(extensions): W3 — ReconcileFromDisk + freshness-as-aggregate-query (swamp-club#252) (#1322)
Summary
Implements W3 of the extension catalog rearchitecture — the third workstream after W1a/W1b/W2:
-
ReconcileFromDiskService (
src/libswamp/extensions/reconcile_from_disk_service.ts): new application service that walks on-disk source trees across all three origin types (locals, pulled, source-mounted), diffs against persisted aggregate state, and emits RowState transitions. Delegates to per-loaderbundleAndIndexOne(notInstallExtensionService). Features:dryRunseam with structuredReconcileTransitionreturn type, transition-count guardrail (>50% of rows → abort), cold-start trigger viaanyKindNeedsInvalidation(). -
enforceI2 deterministic-winner transform: replaces the
IntraExtensionDuplicateTypethrow with a lexicographic-winner + tombstone-loser transform within the Extension aggregate. Cross-aggregate uniqueness (I-Repo-1) still throwsDuplicateTypeErrorat the repository layer. -
Two-layer freshness model: type resolution is now a trivial aggregate query (
isFresh = state.tag === "Indexed"). State maintenance is split between cold-start reconcile (ReconcileFromDisk) and warm-start incremental detection (findStaleFileswith fingerprint comparison, preserved for the hot-path development workflow). -
UNREADABLE_DEP_SENTINEL removal: renamed to
UNREADABLE_PLACEHOLDER(internal tocomputeSourceFingerprint). Zero remaining references in production code.
Deliberate scope change from plan v3
findStaleFiles retains fingerprint comparison for the warm-start path. The plan's "~20 LOC shim" target was wrong — warm-start incremental detection is load-bearing (12 loader tests verified this). The architecture agent confirmed the revert is correct and permanent. The design doc documents the resulting two-layer model.
Regression tests
Three regression tests proving the rebundle-loop bug class is structurally fixed:
- #208: broken transitive dep → stable state, no rebundle loop
- #209: schema-invalid extension → stable state, no rebundle loop
- #212: cached bundle missing → rebundles once, not in a loop
Pulled reconcile matrix
Three tests covering the pulled-extension reconcile paths:
- New source + lockfile entry → Indexed
- Orphan (no lockfile entry) → Tombstoned
- Missing source + lockfile present → EntryPointUnreadable
Performance benchmark
reconcile_from_disk_bench.ts: 50 local models cold-start = 1.2s, warm-start no-op = 7ms (Apple M2 Max). Pre-committed thresholds documented: ≤1.2x ship, 1.2-2x optimize, >2x redesign.
Note: The 1.2s cold-start number is the W3 baseline. A pre-W3 comparison measurement on main should be captured before merge to verify the ≤1.2x threshold.
Test plan
- 13 dedicated ReconcileFromDisk tests (7 generic contract + 3 regression + 3 pulled matrix)
- 3 new enforceI2 transform tests (two-way, three-way, idempotent)
- Updated 1 repository test for W3 transform behavior
- All 5542 tests pass (
deno run test) -
deno check— clean -
deno lint— clean -
deno fmt— clean -
deno run compile— compiled -
UNREADABLE_DEP_SENTINELgrep — zero occurrences - Author smoke: cold-start, warm-start, catalog-deleted recovery on scratch repo + parent repo with real extensions
- Pre-W3 baseline comparison (~20 min, before merge)
- Diversity-matrix soak: Linux + macOS, multiple repo shapes, 24-48h
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260506.190736.0-sha.f040ad12/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/v20260506.190736.0-sha.f040ad12/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/v20260506.190736.0-sha.f040ad12/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/v20260506.190736.0-sha.f040ad12/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260506.185912.0-sha.2fc92714
What's Changed
- feat(vault): add read-secret CLI command (swamp-club#238) (#1325)
Summary
- Add
swamp vault read-secret <vault> <key>CLI command that retrieves and displays secret values from vaults - Wires existing
VaultService.get()to a CLI surface — noVaultProviderinterface changes, every vault already implementsget()for CEL expression resolution - Adds
VaultSecretReaddomain event for audit trail,--forceflag for non-interactive use, and skips confirmation in--jsonmode for agent consumption - Fixes vault skill quick reference table that incorrectly referenced
swamp vault get <vault> <key>(which doesn't exist)
Test plan
- 6 unit tests for libswamp generator (happy path, vault not found, no vaults configured, empty vault name, empty key, secret not found)
-
deno checkpasses -
deno lintpasses -
deno fmtpasses -
deno run compilesucceeds - UAT issue to be filed in
swamp-uatfor CLI coverage - Docs issue to be filed in
swamp-clubforcontent/manual/reference/vaults.md
Closes swamp-club#238
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260506.185912.0-sha.2fc92714/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/v20260506.185912.0-sha.2fc92714/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/v20260506.185912.0-sha.2fc92714/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/v20260506.185912.0-sha.2fc92714/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260506.184342.0-sha.725c7eb1
What's Changed
- test(extensions): remove flaky cross-process lifecycle stress test (#1324)
Summary
- Removes
integration/lifecycle_concurrent_stress_test.ts(introduced in swamp-club#254, commit 5a337b8) - The test runs 50 iterations of 4 concurrent subprocesses (
pull alpha,pull beta,rm alpha,update) and checks catalog↔lockfile↔FS consistency after each iteration - Running
rmandpullfor the same extension concurrently produces inevitable transient inconsistencies due to the asymmetric orderings (install: FS→lockfile→catalog vs rm: catalog→lockfile→FS) — each Windows CI run surfaces a different interleaving failure (version skew, catalog-without-lockfile, etc.) - The unit-level FaultingStubRepository tests already pin the per-service rollback semantics; the cross-process stress test adds cost without reliable signal
Test plan
-
deno checkpasses -
deno lintpasses -
deno fmtpasses - Windows CI passes (the flaky test is removed)
- Linux/macOS CI continues to pass
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260506.184342.0-sha.725c7eb1/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/v20260506.184342.0-sha.725c7eb1/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/v20260506.184342.0-sha.725c7eb1/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/v20260506.184342.0-sha.725c7eb1/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260506.180950.0-sha.58bede5e
What's Changed
- feat(workflows): configurable concurrency limits for fan-out (swamp-club#260) (#1323)
Summary
- Add optional
concurrencyfield at workflow, job, and step levels to cap parallel execution in fan-out scenarios (forEach, parallel jobs/steps) - Implement
Semaphoreprimitive andmergeWithConcurrency()stream combinator that wraps existingmerge()with zero overhead on the unbounded path - Support
SWAMP_MAX_CONCURRENT_STEPSenv var as a host-level ceiling (min(local, global))
Details
Resolution order: step → job → workflow → unbounded. The most-local non-zero value wins. 0 or absent means unbounded (full backward compatibility).
Verified end-to-end: a 10-item forEach with concurrency: 3 correctly batches execution into groups of 3 (max concurrent = 3, ~8s total vs ~2s unbounded).
Files changed
| Area | Files |
|---|---|
| Infrastructure | semaphore.ts (new), merge.ts (+mergeWithConcurrency), libswamp re-export |
| Domain schemas | workflow.ts, job.ts, step.ts — optional concurrency field |
| Execution | execution_service.ts — concurrency resolution + gated merge at both job and step levels |
| Design doc | design/workflow.md — concurrency semantics section |
| Skill | swamp-workflow SKILL.md + forEach reference |
| Tests | 7 semaphore tests, 7 mergeWithConcurrency tests, 5541 total pass |
Follow-up issues
- swamp-club#266 — Docs: document concurrency limits in reference manual
- systeminit/swamp-uat#192 — UAT: adversarial test for workflow concurrency limits
Closes swamp-club#260
Test plan
-
deno check— zero type errors -
deno lint— clean -
deno fmt— clean -
deno run test— 5541 passed, 0 failed -
deno run compile— binary compiled - End-to-end verification:
concurrency: 3caps 10-item fan-out (max concurrent = 3, batched in ~2s intervals) - Backward compatibility: workflows without
concurrencyfield behave identically
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260506.180950.0-sha.58bede5e/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/v20260506.180950.0-sha.58bede5e/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/v20260506.180950.0-sha.58bede5e/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/v20260506.180950.0-sha.58bede5e/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260506.162853.0-sha.65f23545
What's Changed
- fix(drivers): resolve vault sentinels for out-of-process drivers (swamp-club#263) (#1321)
Summary
- Resolve vault sentinel tokens (
__SWAMP_VSEC_*__) inExecutionRequest.methodArgsandExecutionRequest.globalArgsbefore dispatching to out-of-process drivers (docker, custom extension drivers) - The raw driver already resolves sentinels internally via
DefaultMethodExecutionService.execute(), but the non-raw dispatch path sent sentinels through unresolved — models received literal sentinel strings instead of decrypted secret values - Resolution operates on
structuredClonedata from the definition, so persisted state is never mutated
Test Plan
- Added unit test: registers a mock capture driver via
DriverTypeRegistry, dispatches throughexecuteWorkflowwith aVaultSecretBagcontaining sentinel mappings, and asserts the capturedExecutionRequesthas resolved plaintext values — not sentinels - Verified against reproduction scenario: workflow step with
${{ vault.get(...) }}input under docker driver now resolves correctly (sentinel__SWAMP_VSEC_8c14a9ea_0__→ redacted***) - All 5528 existing tests pass
deno check,deno lint,deno fmtclean
Closes swamp-club#263
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260506.162853.0-sha.65f23545/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/v20260506.162853.0-sha.65f23545/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/v20260506.162853.0-sha.65f23545/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/v20260506.162853.0-sha.65f23545/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260505.231643.0-sha.5a337b81
What's Changed
- test(extensions): cross-process concurrency stress for W2 lifecycle services (swamp-club#254) (#1320)
Summary
Closes swamp-club#254. Post-merge follow-up to PR #1310 (W2 — lifecycle services).
Adds integration/lifecycle_concurrent_stress_test.ts that mirrors the swamp-club#234 race-regression pattern at integration/data_delete_test.ts. 50 iterations × 4 concurrent CLI subprocesses (extension pull @stress/alpha, pull @stress/beta, rm @stress/alpha, update) verify the W2 services' per-extension-atomicity claim under cross-process contention.
Five invariants per iteration:
- (i) Catalog ↔ lockfile bijection: every Indexed
_extension_catalog.dbrow for@stress/*matches a lockfile entry by name+version, and vice versa. - (ii) No
DuplicateTypeErrorleakage across distinct fixture types (@stress/alpha-modelvs@stress/beta-model). Fixtures deliberately declare distinct type ids — sharing them would surface a real cross-extension collision on every concurrent install of alpha+beta and the test would mis-categorise it as benign. - (ii') No lockfile-retry-budget exhaustion (
Could not acquire lock on upstream_extensions.json) or SQLite-busy contention (SQLITE_BUSY,database is locked). These are real failures, not benign race outcomes. - (iii)
extensions/models/upstream_extensions.jsonis well-formed JSON after every iteration. - (iv) No orphan files under
.swamp/pulled-extensions/@stress/...— every file is referenced by some lockfile entry'sfiles[]. Empty leftover directories are tolerated;extension rmonly prunes the deepest empty directory, matchingintegration/extension_rm_test.ts's file-level absence assertion.
The harness ships a co-located Deno.serve fixture registry implementing the four ExtensionApiClient endpoints the install path requires (/{name}, /{name}/latest, /{name}@{version}/download, /checksum) so the test runs offline and deterministically. Inline comments at each fake endpoint reference the corresponding extension_api_client.ts callsite so future API drift fails this test loudly rather than silently shipping bad production code.
design/extension.md "Crash-state recovery" section gains one paragraph pinning this test as the load-bearing concurrency-claim verification, with inline swamp-club#254 and test-path references.
Test Plan
-
deno checkclean -
deno lintclean -
deno fmt --checkclean - New stress test runs 3× back-to-back green: 2m58s, 2m57s, 2m57s
- Full suite (
deno run test) green: 5527 passed / 0 failed / 28 ignored (3m15s) -
deno run compilebuilds binary - Wall-clock cost: 2m57s for 50 iterations × 4 children. data_delete_test.ts is 1m39s for 50 × 2 children — ~1.78× ratio, expected scaling.
Notes for reviewers
- Both load-bearing constants (
RACE_ITERATIONS = 50,CONCURRENCY_PER_ITERATION = 4) are pinned with code-comments referencing swamp-club#254 — please don't shrink them silently. - N=50 is mirrored from
data_delete_test.ts(where it was calibrated against an evidence-base) but this test verifies the absence of a race in code claimed safe — there's no calibration point. The header comment is honest about this; CI's natural soak across the merge train is the longer-tail coverage.
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260505.231643.0-sha.5a337b81/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/v20260505.231643.0-sha.5a337b81/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/v20260505.231643.0-sha.5a337b81/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/v20260505.231643.0-sha.5a337b81/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/swamp 20260505.203422.0-sha.d67b0d35
What's Changed
- fix(persistence): close TOCTOU windows in YAML output repo walkers (swamp-club#249) (#1319)
Summary
Followup to #1307. Closes the two pre-existing TOCTOU windows in the YAML output repo walkers that were flagged as out-of-scope when #1307 merged.
What
Issue 1 — Directory-level TOCTOU in findAll
yaml_output_repository.ts:118 — pre-fix, a NotFound from Deno.readDir(methodDir) (concurrent bulk delete of a method directory) escaped the inner for-await to the outer catch and returned [], discarding outputs already collected from earlier method directories of the same type. The fix wraps the inner readDir(methodDir) body in its own try/catch that continues on NotFound. The outer catch keeps its role of "type directory itself doesn't exist."
The same shape exists in findAllGlobalSince (line 200) — same race, smaller blast radius (current model type only). Fixed for consistency.
Issue 2 — Per-file TOCTOU in findById and delete
yaml_output_repository.ts:65 (findById) and :195 (delete) — pre-fix, a NotFound from Deno.readTextFile on a non-target file (concurrent delete during iteration) propagated to the outer catch and returned null / aborted the search even when the target existed. The fix wraps the readTextFile + parseYaml + match block in a per-file try/catch that continues on NotFound.
For delete, the loop is restructured as find-then-act: per-file try/catch wraps only the search; notifyDirty + Deno.remove + cleanupEmptyParentDirs run after the loop in the existing outer try/catch, preserving the existing semantic that a NotFound from Deno.remove (target deleted concurrently) is absorbed silently (idempotent delete).
Why no fix in YamlWorkflowRunRepository.findById
The issue body lists this method too, but the existing entry.name.includes(runId) filter at line 78 restricts readTextFile to the target file only — runIds are 36-char UUIDs that never substring-match each other, and atomicWriteTextFile's .{freshUuid}.tmp intermediate files don't contain the runId. The only NotFound readTextFile can raise is for the target's own concurrent deletion, for which returning null is correct. Added an in-place comment documenting this so a future maintainer who removes that filter doesn't silently re-introduce the bug.
Tests
Four new strict regression tests in yaml_output_repository_test.ts, each fails against pre-fix code and passes against the fix:
findAll: method directory deleted mid-iteration is skipped, not fatalfindAllGlobalSince: method directory deleted mid-iteration is skipped, not fatalfindById: non-target file deleted mid-iteration still returns targetdelete: non-target file deleted mid-iteration still deletes target
The "remove file/dir before call" pattern from #1307's tests doesn't strictly trigger the race on most filesystems (readdir returns only currently-existing entries). The new tests use a withPhantomReadDirEntry helper that monkey-patches Deno.readDir for the call's duration to yield a phantom entry pointing at nothing on disk; production code follows up with readTextFile/readDir on the phantom path, getting a real NotFound, deterministically exercising the TOCTOU window. The helper follows the same shape as the codebase's established Deno.env save/restore pattern.
Test plan
- `deno fmt` — clean
- `deno lint` — clean
- `deno check` — clean
- `deno run test` — full suite passes (one unrelated flaky parallel-test in `shutdown_handlers_test.ts` SIGINT-on-POSIX, passes in isolation both with and without these changes)
- All four new tests verified failing pre-fix and passing post-fix via stash/unstash cycle
- `deno run compile` — binary refreshed
🤖 Generated with Claude Code
Installation
macOS (Apple Silicon):
curl -L https://github.com/systeminit/swamp/releases/download/v20260505.203422.0-sha.d67b0d35/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/v20260505.203422.0-sha.d67b0d35/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/v20260505.203422.0-sha.d67b0d35/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/v20260505.203422.0-sha.d67b0d35/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/