Repository navigation
Description
Summary
In the charges producer's Gmail path, a message whose message_sent_at.user_local is empty/missing and whose fallback date is missing or unparseable does not get skipped — it throws. The per-message date line calls isoDay(new Date(m.date ?? "")), and isoDay is d.toISOString(), which throws RangeError: Invalid Date on an invalid Date. The throw happens before the format guard on the very next lines that was written to skip such messages, and there is no per-message try around it: it propagates out of gmailCandidates, so main()'s catch discards the entire Gmail candidate batch for the period (every good message already collected is lost with it) and logs a misleading gmail read failed … plaid candidates only. One malformed triage row silently zeroes the Gmail half of the charges evidence for that run.
Evidence (main 5a34fa7)
pm-stack/aggregator/scripts/produce-charge-candidates.ts:57-59—isoDay(d)isd.toISOString().slice(0, 10);toISOString()on an invalid Date throws RangeError.pm-stack/aggregator/scripts/produce-charge-candidates.ts:193— per-message:const at = (m.message_sent_at?.user_local ?? "").slice(0, 10) || isoDay(new Date(m.date ?? "")).TriageMessage(lines 109-115) makes bothmessage_sent_atanddateoptional, so the fallback is reachable with""or any unparseable string.pm-stack/aggregator/scripts/produce-charge-candidates.ts:194-197— the regex guard +skipped++that was meant to handle exactly this case sits after the throwing call and never runs for it.pm-stack/aggregator/scripts/produce-charge-candidates.ts:242-246— the only catch is inmain()around the wholegmailCandidates()call; it logsgmail read failedand continues with Plaid only. The candidates array accumulated insidegmailCandidatesis lost.- Same pattern, operator-input variant:
:165computes the Gmailbefore:bound asisoDay(new Date(Date.parse(end) + 86_400_000))— an invalid--endvalue makesDate.parsereturn NaN and throws the same way, again reported as a Gmail read failure.
Verified this run with a laptop Bun probe using the verbatim isoDay + date-line logic (file md5 0f4dba2a441e7c0894cdd2ca2ceefe81, main 5a34fa7):
control-good-rfc2822 -> 2026-10-27
control-good-userlocal -> 2026-10-28
missing-date THREW: RangeError Invalid Date
garbage-date THREW: RangeError Invalid Date
empty-both THREW: RangeError Invalid Date
batch-all-good -> [ "g1@2026-10-27", "g2@2026-10-28" ]
batch-one-bad THREW: RangeError — whole gmailCandidates result lost
Probe: ~/git/fleet/probe-2026-10-29-laneB-chargeproducer/probe.ts (laptop).
Why it matters
The producer is the only source of Gmail charge candidates for the charges matcher (#1057 pipeline; scheduling tracked separately in #2205). A single triage message with a missing/garbled date field — data the producer does not control — silently drops all Gmail-sourced charges for the period, and the log line blames a "gmail read" failure, sending any investigation toward the CLI/auth instead of the one bad message. The skip-and-count path the author wrote for malformed messages (the regex guard + skipped) is unreachable for precisely the malformed shape it targets.
Acceptance criteria
- A triage message with empty/missing
message_sent_at.user_localand a missing or unparseabledateis counted as skipped (the existingskippedcounter / log), and processing continues with the remaining messages — the rest of the Gmail batch is still produced. isoDay(or its call sites) cannot throw on an invalid Date: invalid input is handled at the date-derivation site, not by the whole-batch catch inmain().- An invalid
--endvalue is rejected with an error naming the argument (or handled equivalently explicitly), not surfaced as a Gmail read failure. - Regression tests cover: missing
date, garbagedate, empty-both message interleaved with good messages (batch survives, good candidates present, skip counted), and the two control shapes (RFC 2822date,user_localpresent) producing the sameatvalues as today.
Dedup
gh searches (open + closed) for produce-charge-candidates / isoDay / charge-producer terms: #2194 (open) is the consumer side — parseChargeCandidates in charges-collect.ts accepting impossible calendar dates — a different file, function, and failure mode; #2205 (open) is about scheduling the producer; #1057 (closed) is the original charges-matching feature. No issue covers the producer's Gmail date derivation throwing and discarding the batch.
Activity
developing-today commentedon Oct 29, 2026
CODE-1250
dezren39 commentedon Oct 29, 2026
🤖 Agent note · filed
dezren39 commentedon Nov 1, 2026
Triage complete (queue task 3663): revalidated against current main
a17ae1362ba8575268bb8550b59427d5664b5316.pm-stack/aggregator/scripts/produce-charge-candidates.ts:57-59callstoISOString()on any Date; the Gmail path at:221can passnew Date(m.date ?? "")before the format/skip guard at:222-225. The exception escapes the message loop and reaches the batch-level catch at:263-270, which drops all Gmail candidates. The existing Bun probe reproduced RangeError for missing, garbage, and empty dates;[good, bad, good]lost the batch; RFC 2822 and user_local controls passed.Fresh GitHub state also shows #3333 separately covers missing/flag-like and invalid/inverted
--start/--endarguments and explicitly distinguishes this Gmail message-date failure. To avoid overlapping work, follow-up #5335 is scoped only to safe per-message date handling and batch-survival tests; CLI argument/date validation remains with #3333.🤖 Agent note · handed off
18 remaining items
dezren39 commentedon Nov 1, 2026
🤖 Agent note · picked up
151995f77(review summary: no findings), mergeable clean, diff = 2 files (+44/−2) matching the pinned gmailMessageDay guard. Merging under the standing merge authority; CI in this repo runs on main.Merge pull request #3439 from developing-today/queue/5335-2448-gmail-…
dezren39 commentedon Nov 1, 2026
🤖 Agent note · done
4501d41ef502a78e129709d2cfae22f640cfa892(verified: main HEAD = same SHA; this issue auto-closed by the merge). Codex review at head151995f77had no findings; diff 2 files +44/−2 (gmailMessageDay guard in produce-charge-candidates.ts + tests); prior shift verified laptop tests 20/20.dezren39 commentedon Nov 1, 2026
🤖 Agent note · picked up