Format: what I tried / what I expected / what happened / how I solved it.
- Tried:
npm i -D vitest(v5, bundled with Vite 8 / rolldown) on native Windows 11, thennpm test. - Expected: tests run.
- Happened: startup error "Cannot find native binding ... npm has a bug related to optional dependencies". A clean reinstall did not help. The real cause was hidden by that message: loading
rolldown-binding.win32-x64-msvc.nodedirectly gave "An Application Control policy has blocked this file." (Windows Application Control blocks unsigned native .node files on this machine.) The misleading npm-bug message cost several minutes. - Solved: use Vitest 3 (Vite 7, esbuild, which runs here) and override rollup with the pure-WASM
@rollup/wasm-nodeviaoverridesin root package.json. Did not attempt to bypass the policy. - Consequence: avoid dependencies with unsigned native addons (rolldown, rollup native, swc) in this repo; check any new tool with a quick run.
- Tried:
api.nhtsa.gov/recalls/recallsByEquipment?make=gracoand/products/equipment/...by analogy with the vehicle endpoints. - Expected: car seat recalls as JSON.
- Happened: HTTP 403
{"message":"Missing Authentication Token"}(API Gateway's answer for unknown routes, which suggests an auth problem when it isn't one). nhtsa.gov docs pages return 403 to automated fetches too. - Solved: found via search that child seats/equipment/tires are in the daily bulk flat file
static.nhtsa.gov/odi/ffdd/rcl/, verified it (462 child-seat rows). Documented in docs/data-sources.md.
- Tried:
Recall?format=json&Title=Govee(the docs page listsTitleas a parameter) to look up recalls by brand. - Expected: recalls whose title contains "Govee".
- Happened: HTTP 200 with ALL 10,027 recalls (27 MB). No error, no warning.
ProductName=Goveeworks and returns 1 recall. - Solved: provider uses
ProductName(substring search) for brand and item name, caches per term and caps results; a test assertsTitle=is never used.
- Tried:
aws ssm put-parameter --name /recall-guardian/demo-key ...from Git Bash on Windows. - Expected: parameter created.
- Happened:
Parameter name must be a fully qualified name. MSYS path conversion turned/recall-guardian/demo-keyintoC:/Program Files/Git/recall-guardian/demo-key. - Solved:
export MSYS_NO_PATHCONV=1(scripts/smoke-deployed.mjs sets it for child processes). Lesson: any AWS CLI argument starting with/needs this on Windows Git Bash.
- Tried: plan to set reserved concurrency on the MCP Lambda as a cost/abuse throttle (Function URLs have no built-in throttling).
- Expected: a per-function cap.
- Happened:
aws lambda get-account-settingsshows ConcurrentExecutions=10 and UnreservedConcurrentExecutions=10. AWS requires >=10 unreserved, so no reservation is possible; the account-wide cap of 10 is also shared by every future function (watcher included). - Solved (for now): rely on the demo key (401 for others), 30 s timeout, 512 MB, and turn limits in the simulator. Asked the human to request a Lambda concurrency quota increase (BLOCKERS B3, non-blocking).
- Tried: Converse with Haiku 4.5 and Sonnet 4.6 (worked), then again ~minutes later from the SDK and the CLI.
- Expected: same result.
- Happened:
ResourceNotFoundException: Model use case details have not been submitted for this account ... try again in 15 minutes.for every Anthropic model. Sonnet 5.5 earlier said "not available for this account" (AccessDenied) - a different, less actionable message.list-foundation-modelsshows models as ACTIVE regardless of whether the account can invoke them. - Solved: not solved from our side (account/console action). Tests use a scripted LLM; live test is opt-in (
npm run test:live). Logged in BLOCKERS B1.
- Tried: patching a regex-heavy TypeScript file with
nodescripts written via bash heredocs/node -e(first as a JS template literal, then as String.raw). - Expected: regex source preserved byte for byte.
- Happened:
\bbecame a literal backspace character (code 8) and\s,\.,\dlost their backslash, so a regex silently matched nothing (no error, invisible in the editor). Edit-tool matching against the displayed text failed for the same reason. - Solved: write regex-bearing code only with the file Write/Edit tools; to repair, rebuilt the line with
String.fromCharCode(92)and verified by char codes. Added a unit test per regex (extractDateRange) so this class of bug fails loudly.
- Tried: grouping per-product openFDA records by
recall_number(fixtures looked fine) and syncing the real feeds into one cache. - Expected: every recall gets its own id.
- Happened: the live sync reported 69 "added" for 70 fetched. Some records have
recall_number: "N/A"in BOTH the food and drug endpoints (a mayonnaise packet and a nystatin cream shared the idfda:N/A), so unrelated recalls were merged and silently overwritten. - Solved:
recallKey()falls back toevent_id, then to a content hash; regression test with the real shape. Found only because the opt-in live test (npm run test:live) runs against real data; the fixtures alone did not show it.
- Tried: a 69-item hand-labeled set over a 1,313-recall real corpus to measure matcher precision.
- Expected: labels would be an independent yardstick.
- Happened: the first run scored 90.6% precision; reviewing disagreements, 3 were wrong labels (e.g. a grill-brush model listed in two recalls, tire recalls that need a production year) and 3 were real over-claims. Because I fixed labels while looking at matcher output, the hand-labeled 100% is optimistic. A second, generated set (432 items, 567k pairs, expectations from a coverage policy over the structured fields) found four more real bugs the hand set had missed (production window applied to vehicles, "F-150" matching "F-150 Lightning", filler words in brand names, model-year vs made-year for seats).
- Solved: both sets stay in the test suite, results and the correction history are in docs/matcher-results.md, and the doc says which number is independent.
- Tried: designing the demo story ("a hand-me-down car seat") and asking which data check_item really uses in the deployed Lambda.
- Expected: all three official sources reachable from the tools (the unit tests were green and the heater demo worked).
- Happened: the Lambda's provider only queried CPSC live. Child seats and equipment live in NHTSA's flat file, which only the daily watcher reads, and only for the last 14 days; vehicles need a live NHTSA lookup. A "Graco SnugRide 2012" would have answered "no recalls" for a seat that has a real 2014 recall. Everything was tested with static fixtures, which hid it.
- Solved:
CompositeRecallProvider(live CPSC + live NHTSA vehicle lookup + the watcher's cache) with per-source failure isolation, a one-time child-seat backfill ({"backfill":true}event on the watcher, 71 recalls), and production regression checks inscripts/smoke-deployed.mjs(seat via the cache, vehicle via live NHTSA). Lesson: smoke-test the deployed system with a case per data source, not only the happiest one. - Still not covered in production: food/drug older than the watcher's first 14-day window, and equipment/tire recalls older than that (the cache only holds what the watcher synced). Listed in docs/data-sources.md.
(F6 update) Resolved 2026-10-01 after the human submitted the Anthropic use-case form in the Bedrock console: calls succeeded within ~15 minutes. The earlier brief success was probably a propagation window of a partially processed request.
- Tried: Playwright test that stubs
window.webkitSpeechRecognitionto simulate push-to-talk, and a second test that removes it to simulate an unsupported browser. - Expected: the stub is used; removing the prefixed name means "unsupported".
- Happened: the push-to-talk test timed out and the "no recognizer" test saw an enabled mic. The page uses
window.SpeechRecognition || window.webkitSpeechRecognition, and Chromium defines the unprefixedSpeechRecognitionitself. - Solved: the stub assigns both names; the unsupported case sets both to
undefined. Both paths are now tested (voice.e2e.test.ts).
- Tried: verify the "6% vs 50%" statistic from the CPSC workshop report and transcript instead of trusting search summaries.
- Expected: WebFetch returns the text.
- Happened: the tool reported the PDFs as binary and saved them to disk; the PDF page renderer needs poppler, which is not installed on this machine.
- Solved: a 20-line script with
pdfjs-dist(pure JS) extracted the text; the quotes are in docs/sources.md. It also revealed what the summaries left out: the 6% is consumer-level only, the overall correction rate is 65%.
- Tried: the human registered a recalled product by voice ("we got a ... dresser, model ...").
- Expected: the household panel turns red in the same turn.
- Happened: it stayed green.
add_item/update_itemonly saved the item; an alert existed only after a separatecheck_item, which the model did not always call. - Solved: saving an item with brand and model (or year) now runs the same check and records the alert in the same tool call (one MCP round trip instead of two). The UI unfolds the item with photo and hazard right after the reply.
- Tried: a live check of the human's dresser example against the deployed stack.
- Expected: either the recall or an honest "I can't check right now".
- Happened: saferproducts.gov answered HTTP 503 ("Page Unavailable") for every query; the composite provider swallowed the error by design, so Alexa said "no recalls found", a dangerous answer for a safety product.
- Solved: providers now report which sources were unreachable; check_item / add_item / check_household answer
source_unavailable("I couldn't reach the CPSC recall database just now...") and never call an item clear in that case. Follow-up T9.16: backfill CPSC into the recall cache.
- Tried: string replacements in node scripts after a
git stash/stash pop. - Expected: matches.
- Happened: with
core.autocrlf=true, Git rewrote touched files with CRLF; search strings with\nno longer matched, and one edit silently did nothing (String.replace does not fail on a miss). - Solved:
.gitattributeswitheol=lf, all tracked text files normalized to LF, and the edit helper now throws when a search string is missing.
- Tried: tests for phonetic brand matching against the real CPSC record of the human's example (recall 10998, Aitjunz 8-drawer dressers).
- Expected: the recall lists "Aitjunz" as a brand and "LDQMFJ8D-BK" as a model.
- Happened: brands were ["Yuyitop", "LDQMFJ8D-BR,", ...]: the recalling firm is not the brand on the product, and the quoted SKUs ("SKU "LDQMFJ8D-BR," ... is printed on the packaging") were read as brand names because the sentence says "printed"; the models list was empty.
- Solved: quoted codes (letters + digits, no spaces) are never brands and become models; the sold brand is taken from the product name when the title agrees ("Yuyitop Recalls Aitjunz 8-Drawer Dressers"). The 1,313-recall evaluation still scores the same.
- Tried: the human's dresser conversation, live, against Claude Haiku 4.5 on Bedrock.
- Expected: a reply after add_item.
- Happened: the model ended the turn with an empty message; we stored it and Bedrock rejected every later turn ("The content field in the Message object at messages.3 is empty"), so Alexa lost track of the dresser.
- Solved: an empty final answer is replaced by the last tool's spoken summary, never stored empty, and empty messages in saved sessions are repaired on load (agent.test.ts).
- Tried: a CPSC backfill since 2008 (
LastPublishDateStart=2008-01-01), retried three times by Lambda. - Expected: the full list (it worked for the daily sync until this morning).
- Happened: HTTP 503 "Page Unavailable" every time, in 0.3 s, while
ProductName=andRecallDateStart/Endqueries answered 200 at the same moment. EveryLastPublishDatequery fails, with or without an end date; this also broke this morning's daily CPSC sync ("feed cpsc: ERROR"). - Solved: the feed and the backfill query by recall date (
RecallDateStart/RecallDateEnd), the backfill one quarter at a time with retries. Cost: revisions of old recalls are no longer picked up by the daily sync; new recalls are.
- Tried: the CPSC backfill on the deployed watcher Lambda, in recall-date quarters with retries and window splitting.
- Expected: the windows that work from a laptop to work from Lambda.
- Happened: from Lambda, almost every CPSC request answered HTTP 503, including the small
ProductName=dresserlookup of the MCP server, while the same URLs answered 200 from a home connection at the same minute (with Node's fetch and with curl, any User-Agent). The first run timed out at 10 minutes and Lambda's async retries would have kept hammering CPSC for hours. - Solved: (1) watcher async retries off and stale events dropped after an hour (CDK
retryAttempts: 0,maxEventAge), so a failing backfill cannot hammer a struggling API; (2) the backfill runs from a laptop with the same code (scripts/backfill-cpsc-local.mjs, writing to the project table with the CLI profile) and marks the copy fresh; (3) the cache covers CPSC outages only while its copy is less than a week old, otherwise Alexa says it could not reach CPSC. Open: the daily CPSC sync from Lambda can still fail; if it keeps failing, the copy goes stale and the honest answer comes back.
- Tried: testing our event stream encoder (browser to Amazon Transcribe over WebSocket) against the base64 examples in the developer guide ("Setting up a streaming transcription").
- Expected: decode both examples and re-encode them byte for byte.
- Happened: the signed-frame example checks out completely (both CRC32s, timestamp and byte-array headers). The audio-event example is garbled: its length and prelude CRC are right, but its first header starts with the bytes "M7#" instead of a name length of 13 and ":", and its message CRC does not match.
- Solved: the tests use only the valid example (
transcribe.test.ts); the live test streams real audio to Transcribe, which is the real proof.
- Tried: adding a baby-gear brand pattern (
/\b(car ?seats?|...)\b/i) through a node script in a shell heredoc. - Expected: "car seat" matches.
- Happened: it never matched. The file looked right in the editor, but
od -cshowed real backspace bytes (0x08) where\bshould be: the escape was interpreted twice on the way into the file (the same family as F7). - Solved: replaced the bytes, and added
infra/test/source-hygiene.test.ts, which fails on any control character in a source file.
- Tried:
npm run e2e:deployedafter adding follow-up mode, which stops proactive messages from talking over Alexa. - Expected: the simulated car-seat recall announced within a few seconds.
- Happened: never announced. The page marked the new alert as "seen" on the poll that arrived while Polly was still reading the previous (long) reply, so later polls skipped it. Local tests missed it because their fake audio ends after 10 ms.
- Solved: a new alert stays pending until Alexa is quiet; a browser test with a 6-second reply covers it.
- Tried: the human talked to the deployed simulator with Amazon Transcribe.
- Expected: the request sent about a second after they stopped.
- Happened: it kept "listening" until the mic was tapped. The silence timer restarted on every message from Transcribe, but while you are quiet Transcribe keeps re-sending the same partial result, and background noise came back as "Mhm"; so the timer never ran out (until the 45-second stream limit).
- Solved: only new words restart the timer; a result Transcribe marks final shortens the wait to 0.6 s; fillers ("mhm", "uh") are ignored and not sent. Browser test with a fake socket that repeats itself.
F24 - Recall lookups without a time limit, and "thinking" frozen on Windows (2026-10-03, human test)
- Happened: (1) Alexa said "I'm having trouble reaching the database": a live CPSC request from AWS could hang until the Lambda timed out, taking the whole tool call with it. (2) While Alexa was working, the typing dots and the thinking ring did not move: Windows "animation effects" off makes Chrome report reduced motion, and our reduced-motion rule stopped every animation, including the ones that say "I'm working".
- Solved: every live lookup gives up after 6 s (then that source counts as unavailable and the others still answer); state indicators keep a slow pulse under reduced motion (tested with Playwright's reducedMotion emulation).
- Found while mapping sources for the Open Recall Format: the recallsByVehicle API sends
ReportReceivedDateas28/05/2020(campaign 20V314000). We parsed slash dates as month first, so most vehicle recalls got an impossible date (2020-28-05), and our test fixture04/11/2020(campaign 20V682000, received in November) "passed" as April 11. - Lesson: a fixture can only confirm a format if a value in it is unambiguous; check with a day above 12.
- Solved: day-first parsing for slash dates, tests with
28/05/2020.
- Tried: rehearsing the new video script in text mode against the deployed system (scripts/rehearse-demo.mjs).
- Expected: after the proactive "Heads up: your car seat has a recall. Want me to walk you through the fix?", the answer "yes" walks through the car seat fix.
- Happened: Claude answered about the ice-cream lot code it had asked for earlier. The proactive warning is built and spoken by the page, so it never reached the model's conversation.
- Solved: the page sends what it announced with the next message (
announced), and the server puts it in front of the user's words as the assistant's own earlier words; the system prompt says how to read it. Browser test checks the page sends it once; 4 rehearsals in a row passed.
- Happened: 1 time in 8, Claude called add_item with brand "eight-drawer", so instead of "Who makes it?" Alexa asked for a model number.
- Solved in the server, for any assistant: a "brand" made only of descriptive words (numbers, drawer, white, wooden, double...) goes back into the product name and the tool asks who makes it ("8th June" stays a brand). The video line also says just "a dresser".
- Tried: "we got a second-hand Fisher-Price Rock n Play sleeper" and "a Peloton Tread Plus" on the deployed system.
- Expected: both recalled (CPSC 2019 and 2021, after deaths).
- Happened: Alexa said they were clear. Three separate bugs: (1) the owner's brand "Fisher-Price" is normalized to the alias "fisherprice" but recall text to "fisher price", so no hyphenated brand ever matched; (2) "+" was dropped, so "Tread+" could not be told from the separate "Tread" recall, and a model name was matched against prose that mentioned the other product ("this product is different than the Peloton Tread+"); (3) for items with brand but no model, the save step treated any status other than "need info" as "no recalls", which became wrong once an "All models of ..." recall could be confirmed without a model.
- Solved: aliases applied to recall text too; "+" is the word "plus"; model names without digits must appear in the title or product names; "all models" recalls are confirmed by brand and product; the save step only says "no recalls" on an explicit no_recall. Real CPSC records added as fixtures with matcher and tool tests (the tool test fails without the fix). The matcher evaluation and the blind challenge set are unchanged.