Repository navigation
Description
aggregator(charges-producer): a trailing or flag-like --start/--end/--out value is silently replaced by the defaults — the producer overwrites the live candidates file for the wrong period and exits 0
Summary
pm-stack/aggregator/scripts/produce-charge-candidates.ts reads its three
value-taking flags through argValue() (:52-55), which returns
process.argv[i + 1] — or undefined when the flag is last. In main()
(:252-257) every result is combined with ?? defaults:
const start = argValue("--start") ?? isoDay(<first of current month>);
const end = argValue("--end") ?? isoDay(now);
const outPath = argValue("--out") ?? process.env.CHARGE_CANDIDATES_PATH ??
join(homedir(), "workspace/goals/keep-the-four-repos-healthy/hidden_files/charges-candidates.json");So a flag that is present but missing its value is indistinguishable from a
flag that was never passed: no error, no warning, exit 0. There is also no
validation that --start/--end are real YYYY-MM-DD dates, or that
start <= end. The candidates file is the live input the fleet-feed collector
consumes (collect-fleet-feed.ts: charges-candidates.json → matcher →
charges/chargesAttention), and this producer is run by hand (#2205: it "only
refreshes when an agent runs it by hand"), so a typo lands silently in the
one file that matters.
Evidence (verified this run, main d64e83c1)
Laptop probe ~/git/fleet/probe-2026-10-31-laneA-chargeargs/ — the real
script (sha256 999f2dd904924bc718aa278f209441656e85da05db75aa33c6a95e6d8b0eefe3,
identical on the sandbox main checkout and the laptop checkout), Bun 1.4.2,
with PLAID_CLI pointed at a stub that logs its arguments and returns one
valid transaction, and CHARGE_CANDIDATES_PATH pointed at a probe-local
default file:
| Case | Result |
|---|---|
control: --skip-gmail --start 2026-09-01 --end 2026-09-30 --out <probe>/control.json |
exit 0; control.json written with period 2026-09-01..2026-09-30; stub logged --start-date 2026-09-01 --end-date 2026-09-30; default file untouched |
trailing --out: ... --start 2026-09-01 --end 2026-09-30 --out |
exit 0; silently wrote the default candidates path (wrote .../default-out.json) — in a real run that is the live hidden_files/charges-candidates.json, overwritten when the operator asked for a custom file and forgot its value |
trailing --start: --skip-gmail --end 2026-09-30 --out <probe>/trailstart.json --start |
exit 0; period became 2026-11-01..2026-09-30 (start after end); the stub was queried with --start-date 2026-11-01 --end-date 2026-09-30, and the inverted period was persisted into the written file |
flag-like --out value: --start 2026-09-01 --end 2026-09-30 --out --skip-gmail |
exit 0; wrote a file literally named --skip-gmail in the working directory (producer: wrote --skip-gmail), and the same token also switched Gmail off |
unvalidated date: --skip-gmail --start banana --end 2026-09-30 --out <probe>/banana.json |
exit 0; stub queried with --start-date banana; the written file's period.start is the string "banana" |
Why it matters
The failure is silent in exactly the dangerous direction: the operator
believes they produced a custom period into a scratch file, while the
script actually (a) regenerates the current month into the live
candidates file, or (b) persists a candidates file whose declared period is
inverted or not a date at all, which the collector then matches and surfaces
as if it were the requested period. Nothing in the output line looks wrong —
it prints the substituted values as if they were requested.
Distinct from: #3295 (append-usage-snapshots --by voiding observedBy),
#3330 (backfill --dir crash / flag-named directory), #3285 (compact
--months 0) — same unchecked-argv family, different script, different
failure: here the ?? fallback masks the missing value instead of
crashing or misdirecting, and the target of the silent write is the live
charges pipeline input. Also distinct from #3224/#3120/#3121 (amount/subject
parsing inside this same producer) and #2448 (a garbled Gmail date throwing
inside gmailCandidates).
Acceptance criteria
--start,--end, or--outwith no following value, or with a
following token that starts with--, prints a usage/error message
naming the flag and its missing value to stderr and exits 2 — no Plaid or
Gmail call is made and no file is written (in particular the default
candidates file is untouched).--start/--endvalues that are not real calendar dates in
YYYY-MM-DDform, or astartafterend, exit 2 with an error naming
the offending value — nothing is queried or written.- Valid explicit flags behave exactly as today (same period, same output
file, same summary line); the no-flag defaults (current month, env /
default candidates path) are unchanged. - A regression test (or script-level harness case, e.g. with a stub
PLAID_CLI) covers the trailing-flag, flag-like-value, and
invalid/inverted-date cases.
Activity
dezren39 commentedon Nov 1, 2026
🤖 Agent note · filed
dezren39 commentedon Nov 1, 2026
Adopted as Phase 2 of design #3352 (corollary 3: present-but-valueless is not absent). 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).
20 remaining items
dezren39 commentedon Nov 3, 2026
🤖 Agent note · picked up
fix(aggregator): validate charges producer CLI args (#3333)
dezren39 commentedon Nov 3, 2026
🤖 Agent note · handed off — PR opened
basic: proxy port fallbacksPR/merge-group check did not trigger:.github/workflows/nix-ci.ymlhas only push-to-main, schedule, and workflow_dispatch triggers. Follow-up issue CI policy PR checks are missing workflow triggers #5149 records this coverage gap; no merge was attempted without the policy check.bun test test/produce-charge-candidates.test.tspasses (43 tests, 113 assertions). Package typecheck has the unrelatedtest/overview.test.tsTS2741 failure, reproduced on clean main.dezren39 commentedon Nov 3, 2026
🤖 Agent note · done — PR opened, not merged
pr_opened; PR fix(aggregator): validate charges producer CLI args (#3333) #5145 is open. The policy-listed PR/merge-group check did not run, so no merge was attempted. CI trigger coverage is tracked in CI policy PR checks are missing workflow triggers #5149; this issue remains open pending PR merge.bun test test/produce-charge-candidates.test.ts— 43 passed, 0 failed. The package typecheck failure is pre-existing on clean main.