Skip to main content
← Back to list
01Issue
BugTriagedSwamp CLIPublicTeam
Assigneesstack72

Relationships

Blocked

#1974 Every swamp command dies at module load when $TMPDIR sits under a package.json declaring "type": "commonjs"

Opened by jpisgeek · 9/3/2026

Description

The compiled binary aborts before executing any command:

error: Uncaught SyntaxError: The requested module 'fzf' does not provide an export named 'Fzf'
import { Fzf, type FzfResultItem } from "fzf";
         ^
    at file:///private/tmp/deno-compile-swamp/src/presentation/renderers/components/search_picker.tsx:23:10

Exit code 1. This is not confined to the interactive search commands. Every command class I tried fails identically, because search_picker.tsx sits in the statically-imported module graph reachable from the entry point:

command with trigger present control
swamp version SyntaxError, rc=1 OK
swamp help SyntaxError, rc=1 OK
swamp doctor extensions SyntaxError, rc=1 OK
swamp model search SyntaxError, rc=1 OK
swamp workflow search SyntaxError, rc=1 OK
swamp vault list SyntaxError, rc=1 OK
swamp data list <model> SyntaxError, rc=1 OK

The user-visible symptom is that swamp appears completely and permanently broken, with an error naming an internal renderer component and no hint that the environment is involved.

Impact

Total loss of the CLI, with no degraded mode. A user in this state cannot run swamp version to identify their build, cannot run swamp doctor to diagnose it, and cannot run swamp update to escape it. The documented first-line troubleshooting step in the troubleshooting skill (swamp update --check) is itself unavailable, so the CLI cannot help diagnose its own failure.

This is easy to fall into on macOS. /private/tmp is an ordinary TMPDIR value, and any npm init or npm install run in a /tmp scratch directory leaves a package.json behind. Verified locally: npm init -y on npm 11.19.0 writes "type": "commonjs" into the scaffold unprompted. So the trigger gets created without the user doing anything unusual, and it persists across reboots.

In our case the binary ran a workflow successfully at 03:06 local and failed at 03:27 with the file byte-identical and untouched (mtime 2026-09-03 02:54:44, sha256 confirmed unchanged before and after). Nothing about swamp changed; only the shell's TMPDIR differed between the two sessions. It reads convincingly as a corrupted install, and we nearly reinstalled on that assumption.

Steps to Reproduce

Minimal, from a working swamp:

mkdir -p /var/tmp/swrepro
printf '{"type":"commonjs"}' > /var/tmp/swrepro/package.json
TMPDIR=/var/tmp/swrepro swamp version     # -> SyntaxError, exit 1
TMPDIR=/var/tmp         swamp version     # -> works

The package.json does not need to be in $TMPDIR itself. A parent directory is enough:

mkdir -p /var/tmp/swanc/sub
printf '{"type":"commonjs"}' > /var/tmp/swanc/package.json
TMPDIR=/var/tmp/swanc/sub swamp version   # -> SyntaxError, exit 1

Analysis

deno compile roots the binary's virtual filesystem at $TMPDIR/deno-compile-swamp, which is visible in the error's module URL. Deno's CommonJS/ESM format detection then walks the real filesystem upward from that path looking for the nearest package.json, finds the unrelated one in the user's temp directory, and honours its "type": "commonjs". The embedded fzf ESM module is consequently parsed as CommonJS, its named exports are not statically visible, and import { Fzf } fails.

The bundled npm snapshot is correct and intact. The manifest embedded in the binary pins "fzf": "npm:fzf@0.5.2", which does export Fzf. The package is not being replaced, it is being misclassified.

Confirmed by elimination, each on a freshly created directory:

$TMPDIR nearest ancestor package.json result
unset works
/var/tmp none works
$HOME/tmpprobe none works
fresh dir, no package.json none works
fresh dir + {"type":"module"} type module works
fresh dir + {"name":"x"} no type field works
fresh dir + {"type":"commonjs"} type commonjs fails
/private/tmp (npm-generated) type commonjs fails
/tmp (same inode as above) type commonjs fails

Two plausible hypotheses were tested and ruled out, recorded so nobody retries them:

  • A stray node_modules in the temp directory. One does exist on this machine at /private/tmp/node_modules, but planting an equivalent under a clean TMPDIR did not reproduce the failure, and removing it did not change the result.
  • A stale or corrupt Deno cache. Running with DENO_DIR pointed at an empty directory reproduced the failure identically.

Suggested fixes

Ordered most valuable first:

  1. Make the compiled binary's module-format resolution ignore package.json files outside its own virtual filesystem. The embedded graph's format is known at compile time and should not be re-derived from whatever happens to sit above $TMPDIR on the host.
  2. Failing that, place the VFS root somewhere user scratch files cannot shadow it — a swamp-owned directory such as ~/.swamp/run/, or a randomised subdirectory — rather than directly under $TMPDIR.
  3. At minimum, catch module-load failures at the entry point and emit an actionable message naming $TMPDIR and the offending package.json, so that swamp version and swamp doctor survive to report the problem.

Environment

  • swamp 20260903.004401.0-sha.f2eefb58. swamp update --check reports up to date, so there is no newer build to escape to.
  • Installed at ~/.swamp/bin/swamp, symlinked from ~/.local/bin/swamp, 304212866 bytes, mtime 2026-09-03 02:54:44 -0400.
  • macOS 26.6.2 (build 25G83), arm64.
  • Login shell exports TMPDIR=/private/tmp.
  • Offending file: /private/tmp/package.json, an npm init leftover dated 2026-08-30, containing "type": "commonjs".

Workaround

Point TMPDIR at a directory with no "type": "commonjs" ancestor, or remove the stray package.json:

TMPDIR=/var/tmp swamp <command>
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 2 MOREREVIEW

Triaged

9/4/2026, 4:02:01 AM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack729/4/2026, 3:58:17 AM
stack72 marked as blocked9/4/2026, 3:52:48 PM
Editable. Press Enter to edit.

jpisgeek commented 9/3/2026, 11:12:58 AM

Second sighting of the #1974 failure shape on the same machine, different trigger, same architectural cause: work done at module-import time turns an unrelated environmental fault into total loss of the CLI, swamp version and swamp doctor included.

Being upfront about ownership: the fault that triggered this is mine, not yours. My host's keychain was wedged by an unanswered SecurityAgent authorization prompt. That part is not a swamp bug and I am not asking you to fix it. I am filing because of how swamp reacted to it.

Symptom

Every command, rc=1, no output:

error: Uncaught (in promise) Error: Failed to load platform certificates: [REDACTED-SECRET-1]
const wsHttpClient = Deno.createHttpClient({});
                          ^
    at file:///var/tmp/deno-compile-swamp/src/cli/remote_run.ts:423:27

Confirmed against version, help, auth status, model search, and issue search. Independent of $TMPDIR, so distinct from #1974's trigger.

The clean isolation

This is the part worth your time. The failure is specific to the system trust store:

result
DENO_TLS_CA_STORE=mozilla swamp version works
DENO_TLS_CA_STORE=system swamp version fails
DENO_TLS_CA_STORE=mozilla,system swamp version fails

Network and TLS were healthy throughout — curl https://swamp-club.com returned HTTP 200 in 0.4s while swamp could not start. So this is not connectivity, it is the platform trust store load specifically. Anyone stuck here can escape with DENO_TLS_CA_STORE=mozilla, which is a far better answer than the reinstall I nearly performed.

For completeness on the host fault: SecurityAgent pid 82972 alive 6h37m, first blocked keychain client one second after it spawned, security list-keychains and find-certificate hanging on a 12s timeout while security default-keychain returned instantly.

Why it is still a swamp-side issue

swamp version does not need an HTTP client and swamp --help does not need the platform trust store. Both die anyway, because Deno.createHttpClient({}) runs at module scope in src/cli/remote_run.ts, inside the eagerly-imported graph. It surfaces as an unhandled promise rejection rather than a handled error, so there is no diagnostic path and no actionable message.

That is exactly #1974: there, an eager import of search_picker.tsx propagated an fzf format-detection failure into every command. Here an eager createHttpClient propagates a platform-trust failure into every command. Two unrelated environmental faults, one architectural cause, and both times the casualties include the self-diagnosis commands your own troubleshooting skill tells users to reach for first.

Suggested fix

Extends suggestion 3 on #1974:

  1. Defer HTTP client construction to first use rather than module scope. No command that makes no network call should be able to die on certificate loading.
  2. Audit the eagerly-imported graph for other import-time side effects. Both faults found so far were reached from commands that had no business touching them.
  3. Surface startup failures as handled errors with actionable text, not uncaught promise rejections. At minimum version and doctor should survive any environmental fault.

Environment

  • swamp 20260903.004401.0-sha.f2eefb58, update --check reported up to date before the fault began
  • macOS 26.6.2 (build 25G83), arm64

Two different gators bit me in the same swamp today and neither one was yours — but both times swamp went under with me instead of throwing me a rope. That rope is all I am asking for.

jpisgeek commented 9/3/2026, 11:13:38 AM

Correction to my previous comment, and an unintended bug report.

Your secret-redaction filter ate the most important token in it. The line now reads:

error: Uncaught (in promise) Error: Failed to load platform certificates: [REDACTED-SECRET-1]

What belonged there is a public Apple API symbol, not a secret: SecTrustSettings immediately followed by CopyCertificates, from Security.framework. Written as one word it apparently looks enough like a credential to get scrubbed. I have split it here so it survives.

The CLI did tell me it had redacted something, so nothing was hidden from me — credit where due. But the practical effect is that a bug report loses the exact symbol a maintainer would grep for, and the author only finds out if they read the JSON. Worth a look at the false-positive rate on long camelCase identifiers, since stack traces and API names are exactly what bug reports are made of.

No secret was present in that comment, or in this one.

I came here to report one thing and stepped on two more on the way out. Very on-brand for a swamp.

stack72 commented 9/4/2026, 3:53:33 PM

This is blocked right now - it's been isolated to an upstream deno issue - https://github.com/denoland/deno/issues/36771

Sign in to post a ripple.