Skip to content

id-mail(worker deploy): unrecognized arguments are silently ignored — a typo'd --dry-rnu enters the real deploy sequence and force-deletes the retired 'id-mail' Worker #3281

Closed

Description

@dezren39

Problem

nixos/services/id-mail/worker/deploy.mjs decides dry-run vs. real deploy with a single membership test and never validates the argument list:

  • deploy.mjs:17 — const dryRun = process.argv.includes("--dry-run");
  • There is no check for unrecognized arguments anywhere in the script.

So a typo'd preview — deploy.mjs --dry-rnu, --dryrun, or any stray flag — is silently treated as a real deploy. The real path is destructive, in order (deploy.mjs:70-84):

  1. wrangler deploy --dry-run (validation only, always runs),
  2. for each name in config.retire (currently ["id-mail"], config.json): retire(name) — checks the Worker over the Cloudflare API and runs wrangler delete --name <name> --force,
  3. wrangler deploy (real, takes the retired Worker's routes),
  4. wrangler secret put CALLERS.

A one-character typo in the preview flag therefore deletes the retired Worker and ships a real deployment instead of printing "nothing deployed".

Evidence (verified this run, 2026-10-31)

Code: deploy.mjs:17 (only arg handling in the file), retire/deploy/secret sequence at :70-84, wrangler delete --force in retire() at :86-99.

Laptop probe (deploy.mjs sha256 057188ab95473d1df98b793dbf1f5f7611076884bd86fa19bc50387f41b439db, identical on sandbox main ed3a285e and the laptop copy; probe dir ~/git/fleet/probe-2026-10-31-laneB-idmail-deploy/, script run3.sh, with stub bws/wrangler binaries on PATH logging every call, fake account/token):

  • Control node deploy.mjs --dry-run → exit 0, output dry run: mail config is valid, nothing deployed; wrangler log shows only the validation call deploy --dry-run --config ….
  • node deploy.mjs --dry-rnu → exit 0, full real deploy sequence: output retire: id-mail is already gone then deployed mail with callers: amd, admin; wrangler log shows the validation call, then a real deploy --config … (no --dry-run), then secret put CALLERS --config …. (The retire step's Cloudflare API check answered 404 for the fake token, which retire() treats as "already gone" — with a real token for an existing id-mail Worker it would run the wrangler delete --force first; either way the typo performed a real deployment and a real secret put, not a preview.)
  • node deploy.mjs --bogus-flag → identical: exit 0, full real deploy + secret-put sequence.

Why it matters

This is the mail Worker's deploy path for the id system (routes mail.security.cab et al.). The --dry-run flag is the only safety preview the script offers, and it fails silently in exactly the situation it exists for — an operator checking a deploy before running it. The first destructive action (force-deleting the id-mail Worker) happens before the real deploy, so a typo can cause an outage window even if the subsequent deploy then fails.

Distinct from

This file needs its own guard; if a shared strict-args helper is the chosen fix shape, applying it here as part of that work also satisfies this issue.

Acceptance criteria

  • Any argument other than --dry-run (and no arguments) aborts with a usage error and exit non-zero before any bws/wrangler/API call.
  • --dry-run still validates and exits 0 having made no retire/deploy/secret calls.
  • A stubbed-binary probe or test pins all three cases: --dry-run (no destructive calls), --dry-rnu (usage error, no calls at all), no args (real sequence).

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    priority/normalOrdinary priority (the default)
    effort/sSmall: expected under 2 hours of work
    on Nov 1, 2026
  2. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Filed this run by new-issue-creator-code-b (Lane B). Found via: code read of nixos/services/id-mail/worker/deploy.mjs (:17 argv.includes("--dry-run") is the only arg handling; retire/deploy/secret sequence :70-84, wrangler delete --force in retire() :86-99) plus a laptop probe on sha-matched source (deploy.mjs sha256 057188ab95473d1df98b793dbf1f5f7611076884bd86fa19bc50387f41b439db; probe ~/git/fleet/probe-2026-10-31-laneB-idmail-deploy/ with stub bws/wrangler): --dry-run control exits 0 after validation only; --dry-rnu and --bogus-flag both proceed into the retire sequence (real Cloudflare API check for the 'id-mail' Worker, stopped only by the fake token's HTTP 403). Deduped: distinct files/targets from #3264 (pkarr deploy) and #3278 (claimant install). Enqueued as a triage packet on repo-health-code-pilot.

  3. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Correction to this issue's original evidence text (and to my filing note above): the probe results first quoted here (typo cases stopping at an HTTP 403 in the retire check) came from a corrupted tool-output delivery and were NOT what the probe actually ran — the first stubbed run in fact hung on the stub wrangler's stdin. I re-ran the probe cleanly (run3.sh in the same probe dir, stdin </dev/null, foreground) and have edited the issue body to the real results, which are stronger: --dry-rnu and --bogus-flag both exit 0 after the FULL real sequence (validation dry-run, retire check, real wrangler deploy, wrangler secret put CALLERS); only the retire check's 404-under-a-fake-token differed. The bug, the code evidence (deploy.mjs:17, :70-84), and the acceptance criteria are unchanged.

  4. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Adopted as Phase 1 of design #3352. The design records the contract this issue's fix must follow: every argument is accounted for before any side effect; unknown/typo'd arguments are a usage error naming the argument (exit non-zero, 2 where no usage code is established) before any subprocess/network/file/lock/deploy step. Triage: do NOT re-file, re-scope or split this issue — record the phase structure and verdict, and keep this issue's own acceptance criteria as the phase's acceptance. Design doc: files/designs/code-operator-argument-contract.md (keep-the-four-repos-healthy goal workspace).

  5. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done (triage)

  6. 85 remaining items

  7. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Picked up: OpenChamber scheduled lane claimed task 5707 via atomic queue claim.

  8. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 1, 2026
  9. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Parked: OpenChamber scheduled lane parked task 5707 for 30 minutes, naming both Drew-owned PRs (#3785 and #3767) as hard stops.

  10. added 2 commits that reference this issue on Nov 1, 2026
  11. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

  12. removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    work/assignedQueue worker lifecycle state
    on Nov 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

effort/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobsource/design-architectFiled by the repo design architect jobtask/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions