Skip to content

fix(engine): friendly response for GET / on the engine host - #74

Merged
zaridan merged 2 commits into
mainfrom
fix/engine-root-friendly-response
Jul 19, 2026
Merged

fix(engine): friendly response for GET / on the engine host#74
zaridan merged 2 commits into
mainfrom
fix/engine-root-friendly-response

Conversation

@zaridan

@zaridan zaridan commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

What

A request to the bare root of the engine deployment (desk.resonantiq.app/) matched no rewrite and no file, so Vercel answered its raw NOT_FOUND page — 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; per api/index.ts's own doc, a rewrite selects the serving function but leaves request.url as the original client path.
  • src/composition/app.ts: createAppHandler answers GET /:
    • uiBaseUrl configured → 302 to the operator UI origin, Cache-Control: no-store.
    • not configured → 200 {"service":"helpthread-engine","docs":"/api/v1"} (via the standard json() helper, so no-store like every other engine response).
  • src/composition/root.ts: wires config.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 like GET / (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.
  • Any other method on / 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).
  • No /api/* behavior changes; every other non-API path (/favicon.ico, …) is untouched and still 404s at the platform as before.
  • In passing, fixed the stale api/[...path].ts reference in the exact app.ts doc paragraph this change extends — the function has been api/index.ts since HT-43.
  • No HT ticket on file for this papercut (Atlassian connector wasn't available in the authoring session). Happy to retitle branch/PR onto a ticket if you file one.

Verification

  • npm run typecheck — exit 0.
  • npm run lint — exit 0 (224 files).
  • npm test — 51 files, 1075/1075 passed (5 new tests in src/composition/app.test.ts: redirect branch, JSON branch incl. exact body + headers, HEAD parity, HEAD empty-body, non-GET fall-through with delegation assertion).
  • No PR preview exists to curl: this repo's Vercel projects deploy main only — 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

  • New Features
    • Added unauthenticated “bare root” (/) handling for GET and HEAD.
    • When a UI address is configured, GET / now redirects to the UI (and HEAD / mirrors redirect behavior).
    • When no UI address is configured, GET / returns a small service-identifying JSON payload.
    • POST / continues to be handled by the inbox API (not the friendly root response).
  • Tests
    • Added coverage for the root path behaviors (redirect vs JSON) and method handling.

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>
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The 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 / to /api.

Changes

Bare root request flow

Layer / File(s) Summary
Root handler behavior
src/composition/app.ts
Adds optional uiBaseUrl handling for unauthenticated GET and HEAD / responses, with redirect or JSON behavior; other requests remain delegated to the inbox API.
Routing and composition wiring
vercel.json, src/composition/root.ts
Rewrites / to /api and conditionally passes config.uiBaseUrl into the application handler.
Root behavior validation
src/composition/app.test.ts
Tests configured and unconfigured root responses, HEAD behavior, POST delegation, and the updated handler helper options.

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
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: friendly GET / behavior on the engine host.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/engine-root-friendly-response

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 98c294e and c9987b5.

📒 Files selected for processing (4)
  • src/composition/app.test.ts
  • src/composition/app.ts
  • src/composition/root.ts
  • vercel.json

Comment thread src/composition/app.ts Outdated
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>
@zaridan
zaridan merged commit 276d023 into main Jul 19, 2026
5 checks passed
@zaridan
zaridan deleted the fix/engine-root-friendly-response branch July 19, 2026 15:04
@zaridan

zaridan commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

Post-merge live verification (production = dpl_9yYHhcfsA19XM54mQqPVua7GGUBs, built from 276d023):

  • GET /302, location: https://inbox.resonantiq.app, cache-control: no-store — the redirect branch fired (HELPTHREAD_UI_BASE_URL is set on the engine prod env).
  • HEAD / → 302 with identical headers.
  • Unauthenticated GET /api/v1/internal/health → 401 (standard guard) — API routing intact on the new build.
  • /favicon.ico → still the platform 404 — only / changed, as scoped.

Deploy-pipeline note: the merge push did not trigger the Vercel git build (no deployment was created for 276d023; the integration worked through yesterday and project config was untouched — a lost webhook delivery). Recovered with vercel deploy --prod from a clean checkout of the merge commit. Worth watching whether the next merge auto-deploys.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant