Skip to content

feat(api,upload,content): add X-Request-Id and request_id to envelope#10

Merged
isuttell merged 2 commits into
mainfrom
worktree-agent-a14b512aa4365cfc5
May 22, 2026
Merged

feat(api,upload,content): add X-Request-Id and request_id to envelope#10
isuttell merged 2 commits into
mainfrom
worktree-agent-a14b512aa4365cfc5

Conversation

@isuttell

@isuttell isuttell commented May 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Every Worker response (success or error) carries an X-Request-Id header; every error envelope body includes a matching request_id. Inbound X-Request-Id matching ^[A-Za-z0-9_-]{8,128}$ is echoed; anything else is silently replaced with crypto.randomUUID().
  • Codes with documented remediation (rate_limited_actor, rate_limited_workspace, idempotency_in_flight, invalid_idempotency_key, usage_policy_exceeded, file_size_cap_exceeded, file_count_cap_exceeded, revision_size_cap_exceeded) get an error.docs URL when DOCS_BASE_URL is set on the Worker; otherwise the field is omitted.
  • Shared resolveRequestId, buildErrorBody, docsUrlFor, and requestIdMiddleware live in packages/auth/src/request-id.ts so all three Workers stay in sync. errorResponse now takes the Hono context. Golden tests per Worker cover 404/401/409/4xx/429/500 envelope shape, header echo, and docs behavior.

Test plan

  • pnpm verify is green (turbo: 49 tasks, 38 tests across api/upload/content/auth/cli/api-client).
  • Per-Worker golden tests assert request_id matches X-Request-Id and validate docs URL is present when DOCS_BASE_URL is set, absent otherwise.
  • Hosted smoke against PR preview once deploy lands.

CodeRabbit notes

Five findings, three rejected:

  • Accepted: ignore .agent-out/ so generated CR reports never get committed.
  • Rejected: extract shared expectEnvelope test helper into packages/auth. Each Worker owns its own helper (~30 lines, self-contained); cross-app test utilities are not warranted yet.
  • Rejected: set non-empty DOCS_BASE_URL in wrangler.jsonc. Intentional per task brief; Isaac will set real values later.
  • Rejected: add @cloudflare/vitest-pool-workers. Existing in-memory handleRequest(request, env) pattern is the repo standard; no need to change harness for this change.

Co-Authored-By: Claude Opus 4.7 noreply@anthropic.com

Summary by CodeRabbit

  • New Features

    • Global request-id support: responses include X-Request-Id and error bodies carry matching request_id.
    • Optional error docs links when a docs base URL is configured.
  • Improvements

    • Standardized error envelope and context-aware JSON/error helpers across API, content, and upload services.
    • Rate-limit responses include Retry-After where applicable.
  • Tests

    • Expanded end-to-end test coverage validating request-id behavior and error envelopes.

Review Change Stack

@isuttell isuttell temporarily deployed to pr-preview-10 May 22, 2026 03:32 — with GitHub Actions Inactive
isuttell added a commit that referenced this pull request May 22, 2026
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@isuttell isuttell temporarily deployed to pr-preview-10 May 22, 2026 03:32 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 36373b6a-525d-4c00-a2cc-f123a728f36c

📥 Commits

Reviewing files that changed from the base of the PR and between 3bad1b6 and 1807c4e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • apps/api/src/error-envelope.test.ts
  • apps/api/src/index.ts
  • apps/api/wrangler.jsonc
  • apps/content/package.json
  • apps/content/src/error-envelope.test.ts
  • apps/content/src/index.ts
  • apps/content/wrangler.jsonc
  • apps/upload/package.json
  • apps/upload/src/error-envelope.test.ts
  • apps/upload/src/index.ts
  • apps/upload/wrangler.jsonc
  • docs/ops/project-status.md
  • packages/auth/package.json
  • packages/auth/src/index.ts
  • packages/auth/src/request-id.ts

Walkthrough

This PR introduces centralized request-id handling for Hono services by creating a reusable module in packages/auth/src/request-id.ts, then refactors three worker applications (apps/api, apps/content, apps/upload) to use typed contexts, install request-id middleware, and emit standardized error envelopes with request-id headers and optional documentation URLs. Each worker now validates inbound request-id headers, generates fresh UUIDs for invalid values, and includes request IDs in both response headers and error bodies. Configuration is updated across all services with the new DOCS_BASE_URL environment variable, and comprehensive test suites validate error envelope shapes, request-id propagation, and documentation link generation.

Possibly related PRs

  • zaks-io/agent-paste#9: Both PRs modify apps/upload/src/index.ts—specifically createUploadSession and finalizeUploadSession—adding idempotent replay logic before rate-limit accounting in PR #9, while the main PR refactors the same handlers to use AppContext and context-based errorResponse/jsonResponse with request-id/doc URL propagation.
  • zaks-io/agent-paste#5: The main PR's refactor of apps/api/src/index.ts updates the existing authenticated rate-limiting flow (e.g., rateLimitAuthenticatedRequest/429 responses) to be context/request-id aware, which builds directly on the retrieved PR's introduction of native actor/workspace rate-limit checks and 429/Retry-After behavior in the same file/functions.
  • zaks-io/agent-paste#4: Both PRs change the apps/api error path for 409 idempotency_in_flight (via the admin/upload idempotency handling and the shared error-envelope/request-id/docs response formatting).

"I twitch my whiskers at each trace,
I pin a request-id in its place,
If yours is odd, I'll mint anew —
Then hop and stamp it on the view. 🐇✨"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective of the PR: adding X-Request-Id headers and request_id fields to error envelopes across the three Workers (api, upload, content).
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 worktree-agent-a14b512aa4365cfc5

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/upload/src/index.ts (1)

141-145: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard URL-decoding failures in upload path extraction.

Line 144 can throw URIError for malformed percent-encoding, which currently bubbles into a 500. Please handle decode failure explicitly so bad client paths don’t become internal errors.

Proposed fix
 function uploadFilePath(context: AppContext): string {
   const pathname = new URL(context.req.raw.url).pathname;
   const markerIndex = pathname.indexOf(UPLOAD_FILE_PATH_MARKER);
-  return markerIndex === -1 ? "" : decodeURIComponent(pathname.slice(markerIndex + UPLOAD_FILE_PATH_MARKER.length));
+  if (markerIndex === -1) {
+    return "";
+  }
+  const encodedPath = pathname.slice(markerIndex + UPLOAD_FILE_PATH_MARKER.length);
+  try {
+    return decodeURIComponent(encodedPath);
+  } catch {
+    return "";
+  }
 }
🤖 Prompt for 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.

In `@apps/upload/src/index.ts` around lines 141 - 145, The uploadFilePath function
can throw a URIError when decodeURIComponent is fed malformed percent-encoding;
wrap the decode step in a try/catch inside uploadFilePath (after locating
UPLOAD_FILE_PATH_MARKER and slicing the path) and on a decode failure return an
empty string (or other safe sentinel) instead of letting the error bubble up;
keep the existing marker search logic
(pathname.indexOf(UPLOAD_FILE_PATH_MARKER)) and only attempt decoding when
markerIndex !== -1, but catch URIError from
decodeURIComponent(pathname.slice(...)) and return "" to avoid a 500.
🤖 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 `@apps/api/src/error-envelope.test.ts`:
- Around line 46-174: Add a 200-path test that exercises handleRequest for a
successful GET /v1/whoami (using authStub() and workspaceDb() or a DB.getWhoami
that returns a user) and assert that response.status is 200 and X-Request-Id
behavior matches the invariant: when sending a valid x-request-id header the
response header echoes it, and when no/invalid x-request-id is provided the
response includes a freshly minted x-request-id (assert
response.headers.get("x-request-id") exists and differs from a malformed input).
Use the existing helpers (handleRequest, authStub, workspaceDb) to set up the
request and keep assertions minimal.

In `@apps/api/src/index.ts`:
- Around line 994-1007: The OpenAPI contract is out-of-date: the runtime
errorResponse (which builds the body via buildErrorBody and returns via
jsonResponse, includes requestId from getRequestId(context) and docs from
context.env.DOCS_BASE_URL, and emits X-Request-Id header) no longer matches the
ErrorEnvelope schema and headers. Update the OpenAPI ErrorEnvelope schema to
include request_id (and docs or docs_base_url if used) under the error object,
and add X-Request-Id as a response header on error responses; ensure field names
exactly match what buildErrorBody emits and that any generated SDKs will receive
the new fields.

In `@apps/content/src/error-envelope.test.ts`:
- Around line 36-118: Add a happy-path test that requests an existing object and
asserts a 200/HEAD response includes the request id: use signContentToken to
create a valid token, stub ARTIFACTS.get to return a Response with status 200
(or handle a HEAD path), call handleRequest with an inbound "x-request-id"
header, then assert response.status is 200 and that
response.headers.get("x-request-id") matches the inbound id and that
expectEnvelope(response, ...) (or parsing the body) shows error.request_id (or
equivalent envelope field) equals the same id; place this new it(...) alongside
the other tests referencing handleRequest, signContentToken and expectEnvelope.

In `@apps/content/src/index.ts`:
- Around line 419-425: The OpenAPI schema for error responses is out of sync
with actual responses produced by errorResponse: update the ErrorEnvelope
definition in openApiDocument to include the request_id field (string) and the
optional docs field (string or object as used by buildErrorBody), so generated
clients/docs reflect that responses include request_id and sometimes docs;
locate and modify the ErrorEnvelope schema in openApiDocument and any referenced
response components so they match the shape emitted by errorResponse and
buildErrorBody.

In `@apps/upload/src/error-envelope.test.ts`:
- Around line 39-219: Add a success-path test that exercises handleRequest for a
successful POST /v1/upload-sessions (use Env with AUTH via workspaceAuth() and
DB stubbed to return workspaceSession() from createUploadSession and
finalizeUploadSession) and assert that: response.status is 200 (or expected
success code), response.headers.get("x-request-id") is present and, when you
supply a valid "x-request-id" on the Request, it is echoed back; also call
expectEnvelope(response, "<appropriate_success_type>") or parse the body and
assert body.request_id matches the response header. Use the existing helpers
handleRequest, expectEnvelope, workspaceAuth, workspaceSession and
DB.createUploadSession/finalizeUploadSession to locate where to add the new
test.

In `@docs/ops/project-status.md`:
- Line 184: Update the stale numbered references in the document to match the
new ordered range 1–7: find and replace occurrences of the phrases "items 1-8"
and "item 8" (and any similar mentions) with the correct "items 1-7" or the
appropriate single-item reference (e.g., "item 7"), and verify any other
downstream references in the file that mention the original 1–8 range are
updated for consistency.

---

Outside diff comments:
In `@apps/upload/src/index.ts`:
- Around line 141-145: The uploadFilePath function can throw a URIError when
decodeURIComponent is fed malformed percent-encoding; wrap the decode step in a
try/catch inside uploadFilePath (after locating UPLOAD_FILE_PATH_MARKER and
slicing the path) and on a decode failure return an empty string (or other safe
sentinel) instead of letting the error bubble up; keep the existing marker
search logic (pathname.indexOf(UPLOAD_FILE_PATH_MARKER)) and only attempt
decoding when markerIndex !== -1, but catch URIError from
decodeURIComponent(pathname.slice(...)) and return "" to avoid a 500.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 910129fd-a230-41d8-a33f-4238502f7485

📥 Commits

Reviewing files that changed from the base of the PR and between 82579a7 and 3bad1b6.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (16)
  • .gitignore
  • apps/api/src/error-envelope.test.ts
  • apps/api/src/index.ts
  • apps/api/wrangler.jsonc
  • apps/content/package.json
  • apps/content/src/error-envelope.test.ts
  • apps/content/src/index.ts
  • apps/content/wrangler.jsonc
  • apps/upload/package.json
  • apps/upload/src/error-envelope.test.ts
  • apps/upload/src/index.ts
  • apps/upload/wrangler.jsonc
  • docs/ops/project-status.md
  • packages/auth/package.json
  • packages/auth/src/index.ts
  • packages/auth/src/request-id.ts

Comment thread apps/api/src/error-envelope.test.ts
Comment thread apps/api/src/index.ts
Comment thread apps/content/src/error-envelope.test.ts
Comment thread apps/content/src/index.ts
Comment thread apps/upload/src/error-envelope.test.ts
Comment thread docs/ops/project-status.md
isuttell added a commit that referenced this pull request May 22, 2026
PR #2 added `permissions: administration: write` to the cleanup
workflow, but `administration` is not a valid GITHUB_TOKEN scope. GitHub
rejected the workflow at evaluation time, silently dropping every
`pull_request.closed` event for PRs #2--#9 and accumulating eight
stale `preview/pr-N` Neon branches that tripped the 10-branch free-tier
cap (blocks PR #10/#11/#12 deploys with HTTP 422).

Drop the invalid permission and the `deleteAnEnvironment` step that
required it; rename the file so GitHub registers a fresh workflow id
instead of reusing the wedged record; validate the resolved PR number
is a positive integer before deleting anything.

Stale Neon branches still need a one-time operator purge -- agent is
forbidden from calling `neondatabase/delete-branch-action` autonomously.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
isuttell added a commit that referenced this pull request May 22, 2026
Locks in the 'every response carries request-id' contract on happy paths,
not just error envelopes. Addresses CodeRabbit nitpicks on PR #10.
@isuttell isuttell force-pushed the worktree-agent-a14b512aa4365cfc5 branch from 3bad1b6 to 3afb7db Compare May 22, 2026 05:40
@isuttell isuttell temporarily deployed to pr-preview-10 May 22, 2026 05:40 — with GitHub Actions Inactive
@isuttell

Copy link
Copy Markdown
Contributor Author

Re: OpenAPI ErrorEnvelope staleness on apps/api/src/index.ts:1007 and apps/content/src/index.ts:425 — the hand-rolled openApiDocument object is being deleted entirely by PR #15 (Wave 2.3 openapi-zod), which generates the OpenAPI from packages/contracts Zod schemas. The new generated doc already includes request_id and docs on ErrorEnvelope (see packages/contracts/src/common.ts). Patching the soon-to-be-deleted hand-rolled doc would be wasted churn. Closing these two threads with that rationale.

isuttell added a commit that referenced this pull request May 22, 2026
PR #2 added `permissions: administration: write` to the cleanup
workflow, but `administration` is not a valid GITHUB_TOKEN scope. GitHub
rejected the workflow at evaluation time, silently dropping every
`pull_request.closed` event for PRs #2--#9 and accumulating eight
stale `preview/pr-N` Neon branches that tripped the 10-branch free-tier
cap (blocks PR #10/#11/#12 deploys with HTTP 422).

Drop the invalid permission and the `deleteAnEnvironment` step that
required it; rename the file so GitHub registers a fresh workflow id
instead of reusing the wedged record; validate the resolved PR number
is a positive integer before deleting anything.

Stale Neon branches still need a one-time operator purge -- agent is
forbidden from calling `neondatabase/delete-branch-action` autonomously.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
isuttell and others added 2 commits May 21, 2026 22:53
Every Worker response now carries an X-Request-Id header and every error
envelope body includes a matching request_id. Inbound headers matching
the [A-Za-z0-9_-]{8,128} pattern are echoed; otherwise a fresh UUID is
minted. Codes with a documented remediation gain an optional docs URL
when DOCS_BASE_URL is set on the Worker.

Shared resolveRequestId, buildErrorBody, docsUrlFor, and
requestIdMiddleware live in packages/auth so all three Workers stay in
sync. errorResponse now takes the Hono context. Golden tests per Worker
cover 404/401/409/4xx/429/500 envelope shape, header echo, and docs URL
behavior.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Locks in the 'every response carries request-id' contract on happy paths,
not just error envelopes. Addresses CodeRabbit nitpicks on PR #10.
@isuttell isuttell force-pushed the worktree-agent-a14b512aa4365cfc5 branch from 3afb7db to 1807c4e Compare May 22, 2026 05:53
@isuttell isuttell temporarily deployed to pr-preview-10 May 22, 2026 05:53 — with GitHub Actions Inactive
@isuttell isuttell merged commit 1c60a21 into main May 22, 2026
2 of 4 checks passed
@github-actions

Copy link
Copy Markdown

agent-paste PR preview resources were cleaned up. The pr-preview-${context.issue.number} environment is left in place; remove it from the GitHub UI if desired.

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