Skip to main content
← Back to list
01Issue
BugShippedSwamp CLIPublic
Assigneesskunk-ape

Relationships

#2254 swamp invite link -v puts log output on stdout, breaking the pasteable-link contract

Opened by skunk-ape · 9/17/2026· Shipped 9/17/2026

Problem

swamp invite link has one contract that the rest of its design serves: stdout carries the bare URL and nothing else, so swamp invite link | pbcopy yields a pasteable link. The renderer says so in a comment — "Log mode deliberately splits the two streams ... Do not 'tidy' the note onto stdout" — and lab #2180 specified it.

-v / --verbose breaks it. A libswamp debug record lands on stdout, ahead of the URL.

Repro

A stub standing in for POST /api/v1/recruit-link. A real authenticated run reproduces this identically; the stub only removes the account dependency.

// stub.ts
const port = Number(Deno.args[0]);
Deno.serve({ port }, (req) => {
  const url = new URL(req.url);
  if (req.method === "POST" && url.pathname === "/api/v1/recruit-link") {
    return Response.json({ code: "sk00nk", url: `http://localhost:${port}/r/sk00nk` });
  }
  return new Response("not found", { status: 404 });
});
deno run --allow-net stub.ts 8791 &
export SWAMP_HOME=$(mktemp -d) SWAMP_API_KEY=fake-key \
       SWAMP_CLUB_URL=http://localhost:8791 SWAMP_NO_TELEMETRY=1

for flags in "" "-v" "-q"; do swamp invite link $flags >o.txt 2>e.txt; done

Observed on 20260917.193606.0-sha.6bf7f212:

invocation stdout stderr
swamp invite link http://localhost:8791/r/sk00nk the payout note
swamp invite link -v [DBG] invite·link: Fetching recruit link then the URL the payout note
swamp invite link -q http://localhost:8791/r/sk00nk the payout note, still

So swamp invite link -v | pbcopy copies a debug log line.

Cause

initializeLogging() (src/cli/mod.ts:1704) is called without stderrOnly, so LogTape's console sink writes records to stdout — logger.ts:115 picks getConsoleSink() and only falls back to createStderrSink() behind that flag. -v raises the level to debug, and src/libswamp/invite/link.ts:64 (ctx.logger.debug — "Fetching recruit link") then prints to stdout.

--json is unaffected: initializeLogging receives jsonMode: true and the record is suppressed, so swamp invite link --json -v is still valid JSON. Only log mode is broken.

The stderrOnly mechanism already exists — worker_exec_dispatch is the only caller that opts in.

-q is documented as "Suppress non-essential output", and a note about when a recruit pays out is non-essential by any reading. It survives because the renderer emits it with a raw console.error, not through ctx.logger. createContext computes a verbosity, but the renderer is built from ctx.outputMode alone and never sees it. -q sets the log level to error, which silences the logger but not a direct console.error.

Control, same binary, same session: swamp version prints its line, and swamp version -q prints nothing. So -q works generally; this command is the deviation.

These are two ends of one wiring gap. If the console sink went to stderr for this command, the renderer could emit the note through ctx.logger and get -q handling for free.

Suggested fix

Opt invite link into stderrOnly — or make that the default for commands whose stdout is a value rather than prose — and route the payout note through ctx.logger instead of console.error.

--no-color is fine: it already strips the ANSI from the note.

UAT

swamp-uat#383 and swamp-uat PR #394 cover the stdout/stderr split, but only for the default invocation. Neither -v nor -q is asserted, which is how this got through — the CLAUDE.md global-flag rule ("each command's test file includes tests for the global flags that apply to it") had not been applied to this command. Regression tests for both flags belong in tests/cli/invite/link_test.ts once this is fixed.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 11 MOREREVIEW+ 7 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

9/17/2026, 11:05:25 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
skunk-ape assigned skunk-ape9/17/2026, 9:08:39 PM
Editable. Press Enter to edit.

skunk-ape commented 9/17/2026, 9:48:43 PM

UAT is written and blocked on this. swamp-uat#383 / swamp-uat PR #394 carries two tests that assert the documented contract and fail today:

  • swamp invite link -v must keep stdout to the bare URL
  • swamp invite link -q must suppress the payout note

They are deliberately not skipped, so that PR stays red until this ships. Nothing further should be needed on the UAT side — when the fix lands, CI there goes green and the PR merges.

That also means there is a ready-made check for the fix: run the swamp-uat misc shard with SWAMP_CLUB_SOURCE_DIR pointed at a swamp-club checkout that includes swamp-club#1219.

Sign in to post a ripple.