-
Notifications
You must be signed in to change notification settings - Fork 3
🥅 server: handle expected bridge pairing errors #933
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@exactly/server": patch | ||
| --- | ||
|
|
||
| 🥅 handle expected bridge pairing errors |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ import { captureEvent, captureException, setUser } from "@sentry/core"; | |
| import createDebug from "debug"; | ||
| import { and, DrizzleQueryError, eq, isNull } from "drizzle-orm"; | ||
| import { Hono } from "hono"; | ||
| import { HTTPException } from "hono/http-exception"; | ||
| import { validator } from "hono/validator"; | ||
| import { createHash, createVerify } from "node:crypto"; | ||
| import { literal, object, parse, picklist, string, unknown, variant } from "valibot"; | ||
|
|
@@ -128,7 +129,7 @@ export default new Hono().post( | |
| .where(and(eq(credentials.id, referenceId), isNull(credentials.bridgeId))) | ||
| .returning({ account: credentials.account, source: credentials.source }) | ||
| .then(([updated]) => { | ||
| if (!updated) throw new Error("no match found when pairing bridge id"); | ||
| if (!updated) throw new HTTPException(409, { message: "credential pairing failed" }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: The global Suggested FixUpdate the global Prompt for AI AgentDid we get this right? 👍 / 👎 to inform future reviews. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚩 Webhook caller behavior may change with 409 vs 500 Bridge's webhook system may treat 4xx and 5xx responses differently — many webhook providers retry on 5xx (transient server errors) but not on 4xx (client errors indicating the request itself is invalid). Changing from 500 to 409 could stop Bridge from retrying these specific webhook deliveries. This appears intentional given the PR's stated goal of handling "expected" pairing errors, but worth confirming that Bridge's retry behavior for 409 is acceptable for these scenarios (credential already paired, or reference-id not found). Was this helpful? React with 👍 or 👎 to provide feedback. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== confirm throw site =="
rg -n -C3 'throw new HTTPException\(409' server/hooks/bridge.ts
echo
echo "== inspect global error handler behavior =="
rg -n -C8 'app\.onError|captureException\(|unexpected error|555 as UnofficialStatusCode' server/index.tsRepository: exactly/exa Length of output: 1810 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== find HTTPException import source =="
rg -n 'import.*HTTPException|from.*HTTPException' server/hooks/bridge.ts
echo
echo "== search for HTTPException definition in codebase =="
rg -n 'class HTTPException|export.*HTTPException' --type ts
echo
echo "== check if Hono exports HTTPException =="
rg -n 'HTTPException' package.jsonRepository: exactly/exa Length of output: 203 🌐 Web query:
💡 Result: Hono's HTTPException provides a standardized way to throw and handle HTTP errors. Import it from 'hono/http-exception'. To throw an HTTPException: - Basic: new HTTPException(401, { message: 'Unauthorized' }) - Custom response: new HTTPException(401, { res: new Response('Custom', { status: 401 }) }) - With cause: new HTTPException(401, { message: 'Error', cause: originalError }) Throw it in handlers or middleware for validation failures, auth errors, etc. Handling: Use app.onError((err, c) => { if (err instanceof HTTPException) { return err.getResponse; // Returns Response with status and message/res } // Handle other errors, e.g. console.error(err); return c.text('Internal Server Error', 500); }); This catches all unhandled exceptions, including HTTPExceptions from Hono core/middleware. HTTPException extends Error with status, res?, and getResponse method. For structured JSON errors, customize in onError: return c.json({ error: err.message, status: err.status }, err.status); Official docs confirm this mechanism remains current as of 2026. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== check for other error handlers or middleware before app.onError =="
rg -n 'app\.use.*error|middleware.*error|app\.onError' server/index.ts | head -20
echo
echo "== verify HTTPException is imported in index.ts =="
rg -n 'HTTPException' server/index.tsRepository: exactly/exa Length of output: 208 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== check full error handler logic to confirm status extraction won't match 'credential pairing failed' =="
sed -n '284,316p' server/index.tsRepository: exactly/exa Length of output: 1501
Line 132 throws Fix by either:
|
||
| captureEvent({ | ||
| message: "bridge credential paired", | ||
| level: "warning", | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the general rules of this repository, when an expected database record is not found, a 5xx-level error is preferred over a 4xx to indicate a system or data integrity issue. Since
!updatedhere could mean thereferenceId(obtained from Persona) does not exist in thecredentialstable, a 500 error might be more appropriate than a 409 Conflict, unless this specific failure is considered a recoverable state conflict rather than a data integrity issue.References