Lessons Learned & Common Errors
This document captures recurring errors, patterns, and best practices discovered during Factory Core development. Reviewed quarterly; updated whenever new errors emerge.Hard Constraints (Never Violate)
These constraints are enforced by quality gates and prevent silent runtime failures. Violating any of them breaks the CI/CD pipeline or produces mysterious production errors.
Action: Before committing, run:
Common Errors & Resolutions
Error: “Cannot find module ‘@adrper79-dot/auth’ is not in the npm registry”
Root Cause: Packages published out of dependency order. Package A tries to import Package B, but B wasn’t published yet. Example:@adrper79-dot/neon(which depends on@adrper79-dot/logger) is tagged and pushed- CI publishes Neon before Logger is published → 404 on npm registry
- Follow strict dependency order (see CLAUDE.md)
- Tag multiple packages in sequence, but wait for each publish to complete before tagging the next
- Check GitHub Packages UI before tagging next package: https://github.com/adrper79-dot?tab=packages
Error: “TypeScript strict mode: ‘user’ implicitly has type ‘any’”
Root Cause: Hono context variable not declared with proper type augmentation. Example:npm run typecheck locally, fix the type error, then commit.
Error: “403 Forbidden: PUT https://npm.pkg.github.com/@adrper79-dot%2fXXX”
Root Cause: Wrong npm registry or scope mismatch inpackage.json.
Example:
- All packages must use scope
@adrper79-dot - All packages must point to GitHub Packages registry
Error: “ESLint: no-unsafe-assignment”
Root Cause: Vitest v1 (used by this project) does not support generic type parameters onvi.fn(). Adding a generic causes a TypeScript parse error. Without one, ESLint sees the return type as any.
Example:
vi.fn() to a concrete type using as unknown as typeof X — never use the generic syntax in Vitest v1.
Related: See packages/content/src/index.test.ts for complete pattern.
Error: “Hyperdrive connection failed: ECONNREFUSED”
Root Cause: Database connection string is wrong, or Hyperdrive binding name doesn’t match Drizzle config. Example:- In
wrangler.jsonc, define Hyperdrive binding (e.g.,"DB") - In app’s
env.ts, expose binding:export type Bindings = { DB: Hyperdrive } - In Hono context: Use
c.env.DBto query database - In Drizzle migrations: Use
c.env.DB.query()directly
Error: “Rate limit exceeded: too many requests in 60s”
Root Cause: No rate limiting middleware, or middleware is misconfigured. Prevention:- All apps must have rate limiting on auth routes
- All 6 apps must wire
initAnalyticsto track rate limit hits
Error: “‘token’ is not defined”
Root Cause: Usingjsonwebtoken package instead of Web Crypto API (constraint violation).
Example:
Error: “git push hangs forever on Windows (GCM credential prompt)”
Root Cause: Windows Git Credential Manager (GCM) opens an invisible auth dialog that blocks the terminal —git push origin main appears to hang indefinitely.
Diagnosis: git push produces no output and never returns.
Fix — bypass GCM entirely using gh auth token:
credential.helper="" disables GCM for this invocation; the http.extraheader provides the PAT directly as a Basic-auth header.
Prevention: Keep gh CLI authenticated (gh auth status). When pushing from scripts, use this pattern or set GIT_TERMINAL_PROMPT=0 to fail fast instead of hanging.
Error: “create-hyperdrive workflow: Store step fails with 403”
Root Cause: TheGITHUB_TOKEN auto-provided by GitHub Actions does not have permission to write repository secrets. The “Store Hyperdrive IDs as GitHub secrets” step always fails with a 403.
The Create Hyperdrive step always succeeds — the UUID is printed in the logs even when the Store step fails.
Workaround (standard Factory pattern):
- After running
create-hyperdrive.yml, view the workflow logs and copy the UUID: - Hard-code the UUID in the scaffold workflow (
--hyperdrive-id "0c15..."): - Store it manually as a GitHub secret:
secrets:write scope, which should not be stored as a workflow secret (circular risk). The manual step is deliberate.
Error: “Can’t read from console.log in Wrangler logs”
Root Cause: Workers console output goes tostderr, not stdout, due to the streaming model.
Prevention: Use the @adrper79-dot/logger package instead of console.log for structured logging.
Error: “Cannot read property ‘x’ of undefined”
Root Cause: Database query returned no rows, code assumes a row exists. Prevention: Always check for undefined or use optional chaining.Patterns That Work
1. Middleware Chain Pattern
All 6 apps follow this identical pattern insrc/index.ts:
- Error boundary catches all errors (Sentry)
- Analytics is available on every route
- User identification happens after auth succeeds
- Rate limiting tracked per endpoint
2. Environment & Bindings Pattern
All apps expose a consistentenv.ts:
wrangler.jsonc:
3. Error Handling Pattern
All errors inherit from@adrper79-dot/errors:
4. Database Query Pattern
All queries use Drizzle ORM with explicit error handling:Version & Publishing Strategy
Current Canonical Version
All 19 packages are at v0.2.0 as of Stage 6.How to Bump Versions
-
Edit one package’s
package.json: -
Run
npm installto update lock file: -
Commit and tag:
-
Wait for GitHub Actions publish to complete:
- Go to: https://github.com/adrper79-dot/factory/actions
- Check: Publish workflow succeeded
-
Update dependent packages:
- If another package depends on
@adrper79-dot/errors, update itspackage.json - Follow dependency order (see CLAUDE.md)
- If another package depends on
Quality Gate Checklist
Before merging any package:-
npm run typecheck→ 0 errors -
npm run lint→ 0 warnings (checked with--max-warnings 0) -
npm test→ all passing, 90%+ line coverage, 85%+ branch coverage -
npm run build→ no errors,dist/exists - JSDoc on 90%+ exported symbols (check with
npm run docs:check) - No
any,@ts-ignore, oreslint-disablein public APIs - Commit follows
<type>(<scope>): <description>format - Version bumped in
package.json(matches git tag)
.github/workflows/publish.yml and block publish if any fail.
Monitoring & Observability
What We Track
-
Sentry (errors):
- Uncaught exceptions
- API 5XX responses
- Validation errors with field context
- Auth failures (rate limits, invalid tokens)
-
PostHog (user behavior + business events):
- Page views (every route accessed)
- Rate limit hits (tracked per endpoint)
- User identification (sub, tenantId, role)
- Custom business events (signup, purchase, export, etc.)
Why Both Are Needed
Real Example: User reports “can’t log in”. Check:
- PostHog: Did user hit rate limit? (check
auth.rate_limit_exceededevents) - Sentry: Did auth endpoint throw 5XX? (check error logs)
- Together: “Rate limit hit at 3:15pm, 127.0.0.1 had 11 attempts in 60s”
Quarterly Review Checklist
Update this doc if any of the following occur:- New common error discovered → add to “Common Errors” section
- New pattern worked well → add to “Patterns” section
- Hard constraint was violated (was there a reason?) → decide if constraint needs update
- Package update broke something → document in version section
- Deployment failed for new reason → add to troubleshooting
- Security issue discovered → update runbooks
Incident Post-Mortems
2026-04-27 — selfprime.net Outage + Login Broken (prime-self rename)
What Happened (root causes, in order):-
Pages secrets wiped —
prime-self-uihadCF_API_TOKENandCF_ACCOUNT_IDremoved. Every deploy since April 27 silently failed.selfprime.net/became stale and eventually returned 404 when a route expired. -
No index.html —
prime-self-ui/public/hadlanding.htmlbut noindex.html. Once the Pages cache expired,selfprime.net/returned 404 because Pages needsindex.htmlas the root document. -
Worker rename broke hardcoded URL —
prime-self/wrangler.jsoncwas changed fromname: "prime-self"toname: "prime-self-api". The frontend (landing.htmlline ~537) hardcodedhttps://prime-self.workers.dev/auth/login. After the rename, that URL stopped resolving → ERR_NAME_NOT_RESOLVED. -
Wrong URL format — The hardcoded URL
prime-self.workers.devwas never the correct format. Cloudflare Workers URLs are always{name}.{account-subdomain}.workers.dev→prime-self.adrper79.workers.dev. The short form only resolves when you have a custom workers.dev route explicitly enabling it. -
Stale migration block — After reverting the rename, the
wrangler.jsoncstill had a migrations block{ "tag": "v1", "deleted_classes": ["LiveSession"] }. Cloudflare returned[code: 10074]because that migration was already applied to theprime-selfworker in a previous session; it can’t be applied again. -
False “done” declarations — Twice declared a fix “working” based only on CI green (✓), without running
curl. CI green means code compiled. It does NOT mean the endpoint returns 200.
- Created
prime-self-ui/public/index.html(copy of landing.html) — fixes root 404 - Restored secrets to
prime-self-uiGitHub repo (CF_API_TOKEN,CF_ACCOUNT_ID) - Reverted
prime-self/wrangler.jsoncname fromprime-self-apiback toprime-self - Removed stale
migrationsblock fromwrangler.jsonc - Updated
landing.htmlandindex.htmlURL:prime-self.workers.dev→prime-self.adrper79.workers.dev - Added smoke test jobs to both deploy workflows
- Created
docs/service-registry.yml— authoritative map of worker names → URLs → consumers - Added Worker Rename Protocol and Verification Requirement to
CLAUDE.md
Before renaming any worker: Check docs/service-registry.yml, find all consumers, update them first, deploy consumers, THEN rename the worker.
Before declaring a fix done: curl the endpoint and observe the HTTP status with your own eyes. CI green is not sufficient.
Cloudflare workers.dev URLs: Always use the account-scoped form{name}.adrper79.workers.dev. The short form{name}.workers.devdoes not resolve without an explicit workers.dev route.
Pages root document:public/index.htmlmust exist.landing.htmlwill NOT serve as the root.
Secrets are per-repo: Each app repo needs its ownCF_API_TOKENandCF_ACCOUNT_ID. They are NOT inherited from Factory Core.
See Also
- CLAUDE.md — Standing orders & hard constraints
- Service Registry — Worker name → URL → consumer map
- GitHub Secrets & Tokens Runbook — Secrets management
- Secret Rotation Runbook — How to rotate specific secrets
- Deployment Runbook — How to deploy apps
- Getting Started Runbook — First-time setup