Relationships
#582 Extension source: direct-content export scan only reads first 64 KiB, silently drops exports beyond it
Opened by jentz · 6/6/2026· Shipped 6/6/2026
Summary
swamp extension source add with the direct-content layout detects an
extension by scanning each candidate .ts file for an
export const model|vault|driver|datastore|report = … declaration. The scan
only inspects the first 65,536 bytes (64 KiB) of the file. A valid
extension whose export declaration begins beyond that point is silently not
detected — source add reports "No extensions found …" and the extension
never registers.
The same files load and run correctly once published and pulled (the manifest-driven path reads the declared entry directly), so this only bites the local-source "test before publish" workflow.
Environment
- swamp
20260605.235921.0-sha.e3a85856 - macOS (Darwin), Deno runtime
- Reproduced with both the direct-content layout and an equivalent
extensions/<kind>/tree.
Impact
- Large single-file extensions cannot be loaded from a local source. In one
repo, a ~1,000-line model (its
export const modelat byte 33,736) loads fine, while a ~2,500-line report in a sibling dir (itsexport const reportat byte 79,640) is invisible tosource add. - The miss is silent — no
swamp-warning:is emitted about a file that was read but truncated past an export, so it presents as "my extension is broken" rather than "the scanner didn't read far enough".
Reproduction (minimal, deterministic)
Generator — writes a valid report whose export const report begins at a
chosen byte offset:
# gen.py — usage: python3 gen.py <target_byte_offset> <out.ts> <name>
import sys
target=int(sys.argv[1]); path=sys.argv[2]; name=sys.argv[3]
header=b"// padding\n"; opener=b"/* "; closer=b" */\n"
export=(f'export const report = {{\n name: "{name}",\n description: "x",\n'
' scope: "workflow",\n'
' execute: async () => ({ markdown: "ok", json: {} }),\n};\n').encode()
fill=target-(len(header)+len(opener)+len(closer))
open(path,"wb").write(header+opener+b"x"*fill+closer+export)for T in 60000 65000 66000 70000; do
d=$(mktemp -d); python3 gen.py "$T" "$d/report.ts" "@repro/scan$T"
printf 'export@%s -> ' "$T"
swamp extension source add "$d" --only reports --json 2>&1 | jq -r '.action // .error'
swamp extension source rm "$d" >/dev/null 2>&1
doneObserved
export@60000 -> added
export@65000 -> added
export@66000 -> No extensions found at '…'. Expected … files declaring extension exports …
export@70000 -> No extensions found …Edge bisection (byte range the export const report token occupies):
[65510..65529) -> DETECTED
[65517..65536) -> missed
[65525..65544) -> missed
[65536..65555) -> missedThe cutoff is the 64 KiB (65,536-byte) mark — the export token must fall fully within the first 64 KiB to be detected.
Expected
Either (a) scan the whole file — exports are routinely placed at the end, after
types/helpers — or (b) if a read cap is intentional for performance, at minimum
emit a swamp-warning: naming any file that contains an export-like token past
the window, and have the source add "No extensions found" error mention the
size cap, so the failure is not silent.
Workaround
Place the export const <kind> within the first 64 KiB (e.g. near the top of
the file), or split helpers into sibling modules so the entry file stays small.
Related observation (possibly separate root cause)
When the entry in an extensions/<kind>/ tree is a symlink to the real
file, the extension also fails to load; replacing the symlink with a real copy
works. The bundler appears not to follow symlinks. Flagging in case it shares a
root cause with local-source loading; happy to split into its own report.
Shipped
Click a lifecycle step above to view its details.