You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
***http.createServer async callback — unhandled promise rejections crash test server**: (gotcha) \`http.createServer(async (req, res) => { await ... })\` — Node accepts async callbacks but ignores their return value. If the inner \`await\` throws, it causes an unhandled promise rejection that crashes the test server. Fix: wrap entire async callback body in \`try...catch\` and call \`res.end()\` or \`res.destroy()\` in the catch block. Applies to \`test/mocks/server.ts\` and any test HTTP server using async request handlers.
***MastraClient has no dispose API — use AbortController for cleanup**: MastraClient has no \`close()\`/\`dispose()\` API — cleanup via \`ClientOptions.abortSignal\` (constructor) or per-prompt \`signal\`. Without explicit abort, fetch keep-alive sockets hold the event loop alive past natural exit. Pattern in \`src/lib/init/wizard-runner.ts\`: create \`AbortController\` per \`runWizard\`, pass \`abortSignal: controller.signal\` to \`new MastraClient(...)\`, abort via \`using \_ = { \[Symbol.dispose]: () => controller.abort() }\`. Custom \`fetch\` wrapper must preserve \`init.signal\` via spread. Tests capture \`ClientOptions\` via \`spyOn(MastraClient.prototype, 'getWorkflow').mockImplementation(function() { capturedOpts.push(this.options); ... })\`.
61
61
@@ -66,10 +66,10 @@
66
66
***SQLite transaction() ROLLBACK can throw, discarding original error**: (gotcha) SQLite transaction ROLLBACK error-swallowing trap: In \`src/lib/db/sqlite.ts\`, \`transaction()\` catches errors and runs \`this.db.exec('ROLLBACK')\`. If ROLLBACK itself throws, the original error is lost. Fix: \`const origErr = e; try { this.db.exec('ROLLBACK'); } catch (rbErr) { log.debug(...); } throw origErr;\`
***Vitest worker pool requires pool:forks + UV\_USE\_IO\_URING=0 on GitHub Actions**: (gotcha) Vitest worker pool + CI issues: (1) On GitHub Actions, io\_uring crashes Node.js workers (exit 134/SIGABRT). Fix: set \`pool: 'forks'\` in \`vitest.config.ts\` AND \`UV\_USE\_IO\_URING=0\` env var in CI — it's a kernel capability issue, not a Node version issue. (2) Tests internally calling \`Bun.spawn\` must be skipped in Vitest Node workers via \`skipIf\`. (3) npm build smoke test uses system Node — \`setup-node\` (with \`node-version: ${{ matrix.node }}\`) must not be deleted from the npm build CI job; smoke test on \`dist/bin.cjs\` rejects Node < 22.15 and fails silently if setup-node is missing. (4) Vitest 4 removed \`test(name, fn, { timeout })\` signature — options must be second arg: \`test(name, { timeout }, fn)\`. Bare numeric timeout \`beforeAll(fn, 60\_000)\` remains valid.
69
+
* **Vitest worker pool requires pool:forks + UV\_USE\_IO\_URING=0 on GitHub Actions**: (gotcha) Vitest/CI issues: (1) GitHub Actions io\_uring crashes Node.js workers (exit 134/SIGABRT). Fix: \`pool: 'forks'\` in \`vitest.config.ts\` AND \`UV\_USE\_IO\_URING=0\` in CI. (2) Tests calling \`Bun.spawn\` internally must be skipped in Vitest Node workers via \`skipIf\`. (3) npm build smoke test uses system Node — \`setup-node\` (with \`node-version: ${{ matrix.node }}\`) must not be deleted from npm build CI job; smoke test on \`dist/bin.cjs\` rejects Node < 22.15 and fails silently if setup-node is missing. (4) Vitest 4: options must be second arg: \`test(name, { timeout }, fn)\`; bare numeric timeout \`beforeAll(fn, 60\_000)\` remains valid. (5) \`http.createServer(async (req, res) => {...})\` — unhandled rejections crash test server; wrap body in try/catch. (6) \`dorny/paths-filter\` diffs against base — empty commits produce all-false outputs, silently skipping jobs; make a real file change to trigger CI. (7) \`node:sqlite\` requires \`--experimental-sqlite\` on Node 22 — top-level import crashes before any try/catch. (8) Lazy \`require()\` in test fixtures bypasses Vite's \`.js→.ts\` resolver — use top-level \`import\`.
* **whichSync must use 'command -v' not 'which' for PATH-restricted lookups**: (gotcha) Bun→Node.js API replacements: \`Bun.which(cmd,{PATH})\` → \`whichSync()\` from \`src/lib/which.ts\` (uses 'command -v'). \`Bun.spawn\` → \`spawn(cmd,args,{stdio:\['pipe','pipe','pipe'],...opts})\`; \`proc.exited\` → \`new Promise(r=>proc.on('close',c=>r(c??1)))\`; stdout via \`proc.stdout.on('data',(d)=>{out+=d;})\`. \*\*CRITICAL: always attach \`proc.on('error',noop)\` — Node crashes on unhandled spawn errors.\*\* \`Bun.spawnSync\` → \`spawnSync\`; \`proc.success\`→\`proc.status===0\`. \`Bun.write\`→\`writeFileSync\`. \`Bun.sleep(ms)\`→\`import {setTimeout as sleepMs} from 'node:timers/promises'\`. \`new Bun.Glob(p).match(i)\`→\`picomatch(p,{dot:true})(i)\`. \`Bun.randomUUIDv7()\`→\`uuidv7()\`. \`Bun.semver.order()\`→\`compare()\` from \`semver\` (guard with \`semverValid(v)\`). \`Bun.file().writer()\`→\`createWriteStream\`. Node version: \`engines.node >=22.15\` (zstd requires 22.15+). CI builds \`\["22","24"]\`; E2E jobs MUST use \`actions/setup-node\` with \`node-version: 22\`. Tests using \`Bun.spawn\` internally must be skipped in Vitest Node workers via \`skipIf\`. PRESERVE intentional Bun usage: \`Bun.build()\` in \`script/build.ts\` for native binary compilation must stay Bun; \`build-binary\` CI job retains \`oven-sh/setup-bun\`; \`script/nod \[truncated — entry too long]
72
+
* **whichSync must use 'command -v' not 'which' for PATH-restricted lookups**: (gotcha) Bun→Node.js API replacements: \`Bun.which(cmd,{PATH})\` → \`whichSync()\` from \`src/lib/which.ts\` (uses 'command -v'). \`Bun.spawn\` → \`spawn(cmd,args,{stdio:\['pipe','pipe','pipe'],...opts})\`; \`proc.exited\` → \`new Promise(r=>proc.on('close',c=>r(c??1)))\`; stdout via \`proc.stdout.on('data',(d)=>{out+=d;})\`. \*\*CRITICAL: always attach \`proc.on('error',noop)\` — Node crashes on unhandled spawn errors.\*\* \`Bun.spawnSync\` → \`spawnSync\`; \`proc.success\`→\`proc.status===0\`. \`Bun.write\`→\`writeFileSync\`. \`Bun.sleep(ms)\`→\`import {setTimeout as sleepMs} from 'node:timers/promises'\`. \`new Bun.Glob(p).match(i)\`→\`picomatch(p,{dot:true})(i)\`. \`Bun.randomUUIDv7()\`→\`uuidv7()\`. \`Bun.semver.order()\`→\`compare()\` from \`semver\` (guard \`semverValid(v)\`). \`Bun.file().writer()\`→\`createWriteStream\`. Node: \`engines.node >=22.15\` (zstd). CI matrix \`\["22","24"]\`; E2E jobs MUST use \`actions/setup-node\` with \`node-version: 22\`. Migration phases: (1) switch package manager bun→pnpm, (2) replace Bun APIs with Node.js equivalents, (3) replace \`bun run \<script>\` with \`pnpm run\`/\`tsx file.ts\`, (4) remove \`setup-bun\` from CI. Exception: \`build.ts\` uses \`Bun.build()\` — stays on Bun; \`build-binary\` CI job retains \`oven-sh \[truncated — entry too long]
***Whole-buffer matchAll slower than split+test when aggregated over many files**: (gotcha) Grep/scan traps in \`src/lib/scan/\`: (1) Whole-buffer \`regex.exec\` 12× faster per-file but ~1.6× SLOWER over 10k files — early-exit at \`maxResults\` via \`mapFilesConcurrent.onResult\` wins. (2) Literal prefilter is FILE-LEVEL gate (\`indexOf\`→skip); per-line verify breaks cross-newline patterns and Unicode length-changing \`toLowerCase\`. (3) Extractor \`hasTopLevelAlternation\`+\`skipGroup\` must call \`skipCharacterClass\` (PCRE \`\[]abc]\` ≠ JS empty class). (4) Wake-latch race: use latched \`pendingWake\` flag, not \`let notify=null; await new Promise(r=>notify=r)\`. (5) \`mapFilesConcurrent\` filters \`null\` but NOT \`\[]\` — return \`null\` for no-op files. (6) \`collectGlob\`/\`collectGrep\` must NOT forward \`maxResults\` to iterator; drain uncapped, set \`truncated=true\`.
@@ -84,5 +84,5 @@
84
84
85
85
### Preference
86
86
87
-
<!-- lore:019e4a9c-430a-74a8-a5e4-8dd98c672cef-->
88
-
* **Always wait for Sentry Seer and Cursor BugBot CI jobs before merging and address all unresolved review comments**: (preference) PR/CI discipline: Run adversarial review rounds (security, edge cases, error handling, lint, test coverage), severity-tiered (CRITICAL/MEDIUM/LOW/NON-BLOCKING), explicit MERGE/NO-MERGE verdict. Wait for 'Sentry Seer' and 'Cursor BugBot' CI jobs; address all unresolved comments. \`dorny/paths-filter\` diffs against base — empty commits produce all-false outputs, silently skipping jobs; make a real file change to trigger CI. Lint: fix errors immediately with minimal surgical changes — prefix unused/shadowing vars with \`\_\`, use optional chaining. Re-run lint to confirm exit code 0 before committing. \`node:sqlite\` requires \`--experimental-sqlite\` on Node 22 — top-level import crashes before any try/catch. Always exclude \`build/\` and \`dist/\` from Biome analysis to avoid type-limit warnings. Biome version mismatch: local may pass while CI fails — CI is authoritative; apply \`biome format --write\` on failing file. Lazy \`require()\` in test fixtures bypasses Vite's \`.js→.ts\` resolver — use top-level \`import\` instead.
87
+
<!-- lore:019e4cbd-d784-7468-a410-e34b8629df72-->
88
+
***Always honor Retry-After header when present in LLM adapter**: Always honor Retry-After from server in \`backoffMs()\` in \`packages/gateway/src/llm-adapter.ts\`: if \`retryAfterMs != null\`, return \`Math.min(retryAfterMs, cap)\` where cap is \`RETRY\_AFTER\_CAP\_URGENT\_MS=8\_000\` for urgent or \`RETRY\_AFTER\_CAP\_BACKGROUND\_MS=120\_000\` for background. Never ignore Retry-After in favor of computed backoff. TRANSIENT\_CODES={429,500,502,503,529}; MAX\_RETRIES\_RATE\_LIMIT=3, SERVER=3, URGENT=2. Backoff (no Retry-After): 429 background=60s/120s/180s; urgent=min(1000×2^n,4000); 5xx background=min(1000×2^n,8000). Bearer tokens inject \`billingBlock\` as first system block; \`signBody()\` replaces \`cch=00000\` placeholder with xxHash64. System prompt caching uses \`cache\_control:{type:'ephemeral',ttl:'1h'}\`. \`opts.thinking\` NOT forwarded to bare API calls. Circuit breaker tripped on non-urgent 429s via \`tripCircuitBreaker()\`.
0 commit comments