fix(engine): friendly response for GET / on the engine host - #74
Conversation
A request to the bare root of the engine deployment matched no rewrite and no file, so Vercel answered its raw NOT_FOUND page — which reads as "the deployment is dead" while the engine is in fact healthy. vercel.json now rewrites / (exactly /, no other non-API path) to the single engine function, and the composition root's unified handler answers GET / with a 302 to the operator UI when uiBaseUrl (HELPTHREAD_UI_BASE_URL, already optional in config) is configured, else a tiny service-identifying JSON. HEAD / behaves like GET / (RFC 9110 §9.3.2); any other method on / falls through to the inbox API's standard 404 envelope like every other unknown path. No /api/* route changes; no new config requirements. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe application handler now serves the bare root path: configured requests redirect to the UI, while unconfigured GET and HEAD requests return a service JSON response. Other requests delegate to the inbox API, and Vercel routes ChangesBare root request flow
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant Vercel
participant API as api/index.ts
participant Handler as createAppHandler
participant Inbox as deps.inboxApi
Client->>Vercel: Request /
Vercel->>API: Rewrite / to /api
API->>Handler: Forward request
alt GET or HEAD with uiBaseUrl
Handler-->>Client: 302 redirect with no-store
else GET or HEAD without uiBaseUrl
Handler-->>Client: JSON service payload
else Other method or path
Handler->>Inbox: Delegate request
Inbox-->>Client: Inbox response
end
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/composition/app.ts`:
- Around line 124-131: The unconfigured HEAD / branch in the root request
handler must return status 200 with the same headers as the JSON GET response
but no body; update the fallback alongside the existing json() call while
preserving GET behavior. In src/composition/app.test.ts lines 226-231, add
coverage for unconfigured HEAD / that asserts the 200 response headers and an
empty body.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 8a9ed09a-cbbb-4417-8ce1-97c2e1a0496c
📒 Files selected for processing (4)
src/composition/app.test.tssrc/composition/app.tssrc/composition/root.tsvercel.json
CodeRabbit: the unconfigured-uiBaseUrl HEAD / branch returned the JSON body and left RFC 9110 §9.3.2's 'MUST NOT send content' to the Node transport's HEAD suppression. The handler now returns the same status and headers with a null body itself — its contract is a finished Response, whatever serves it. Adds the matching header/empty-body test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Post-merge live verification (production =
Deploy-pipeline note: the merge push did not trigger the Vercel git build (no deployment was created for 🤖 Generated with Claude Code |
What
A request to the bare root of the engine deployment (
desk.resonantiq.app/) matched no rewrite and no file, so Vercel answered its rawNOT_FOUNDpage — which reads as "the site is dead" while the engine is in fact healthy (the maintainer hit exactly this on 2026-07-18).vercel.json: one added rewrite —/(exactly/, no other non-API path) → the single engine function. Same mechanism as the existing/api/:path*rewrite; perapi/index.ts's own doc, a rewrite selects the serving function but leavesrequest.urlas the original client path.src/composition/app.ts:createAppHandleranswersGET /:uiBaseUrlconfigured → 302 to the operator UI origin,Cache-Control: no-store.{"service":"helpthread-engine","docs":"/api/v1"}(via the standardjson()helper, sono-storelike every other engine response).src/composition/root.ts: wiresconfig.uiBaseUrl(HELPTHREAD_UI_BASE_URL, already optional — no new config) into the handler deps, conditional-spread per the house optional-field convention.Deliberate scope notes
HEAD /answers likeGET /(RFC 9110 §9.3.2: identical to GET minus the body). One extra clause beyond the literal "GET /" ask — flagged here; trivial to drop if unwanted. Per CodeRabbit's review, the handler strips the body for HEAD itself (b050bc7) instead of leaving that to the transport./falls through to the inbox API's standard 404 JSON envelope (a path rewrite is method-neutral, so e.g.POST /now reaches the engine instead of the raw Vercel page — same handled-404 outcome as any unknown path)./api/*behavior changes; every other non-API path (/favicon.ico, …) is untouched and still 404s at the platform as before.api/[...path].tsreference in the exactapp.tsdoc paragraph this change extends — the function has beenapi/index.tssince HT-43.Verification
npm run typecheck— exit 0.npm run lint— exit 0 (224 files).npm test— 51 files, 1075/1075 passed (5 new tests insrc/composition/app.test.ts: redirect branch, JSON branch incl. exact body + headers, HEAD parity, HEAD empty-body, non-GET fall-through with delegation assertion).mainonly — verified by zero Vercel deployments/commit-statuses on this PR's head commit and on merged PR feat(engine): inbound observability — health endpoint, forged-token signal, log gaps (HT-44) #73's head commit. The un-unit-testable bit (Vercel edge routing of the new rewrite) uses the identical mechanism the production/api/:path*rewrite has exercised since HT-43. Post-merge live check:curl -i https://desk.resonantiq.app/— I'll run it and report as soon as this merges and deploys.🤖 Generated with Claude Code
Summary by CodeRabbit
/) handling forGETandHEAD.GET /now redirects to the UI (andHEAD /mirrors redirect behavior).GET /returns a small service-identifying JSON payload.POST /continues to be handled by the inbox API (not the friendly root response).