Skip to content

Carry the errno through the proxy server's error payload - #2249

Merged
kmcginnes merged 5 commits into
mainfrom
proxy-server-errno-in-error-payload
Sep 24, 2026
Merged

kmcginnes merged 5 commits into
mainfrom
proxy-server-errno-in-error-payload

Conversation

@kmcginnes

@kmcginnes kmcginnes commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Description

extractErrorInfo in error-handler.ts returned only { status, message } for a plain Error, dropping the errno. The client's createDisplayError already had ECONNREFUSED and ECONNRESET branches keyed on data.code and data.cause?.code, so for anything routed through the proxy server those branches were dead code. An unresolvable hostname and a refused port both surfaced as a generic "Network Response 500".

Two changes:

  • extractErrorInfo carries code and cause.code through the response payload. Each is guarded on typeof === "string" and nothing else is copied, so a stack trace, filesystem path, or any other cause property cannot ride along.
  • createDisplayError gains a branch for ENOTFOUND, ETIMEDOUT, and EAI_AGAIN under one "Database unreachable" message. The wording deliberately does not name who made the failing request, because a non-proxy connection has the browser talking to the database directly.

The existing ECONNREFUSED and ECONNRESET messages are unchanged.

Validation

A new error-handler.test.ts pins the payload shape, including a case whose cause carries stack, hostname, path, and syscall and asserts only code survives. The client-side tests are built from the exact payload extractErrorInfo now sends, so they fail if the two sides drift.

pnpm checks and pnpm test clean: 223 files, 2720 tests.

Related Issues

None.

Check List

  • I confirm that my contribution is made under the terms of the Apache 2.0 license.
  • I have verified pnpm checks passes with no errors.
  • I have verified pnpm test passes with no failures.
  • I have covered new added functionality with unit tests if necessary.
  • I have updated documentation if necessary.

@kmcginnes
kmcginnes added this pull request to stack #2253 September 23, 2026 21:40
Base automatically changed from proxy-server-test-environment-factory to main September 23, 2026 22:12
@kmcginnes
kmcginnes force-pushed the proxy-server-errno-in-error-payload branch 2 times, most recently from b5241db to 917c01e Compare September 23, 2026 23:36
@kmcginnes
kmcginnes removed this pull request from stack #2253 September 23, 2026 23:36
extractErrorInfo dropped a plain Error's code and cause.code, so the
client's createDisplayError could never match the ECONNREFUSED and
ECONNRESET branches it already had. An unresolvable host and a refused
port both surfaced as a generic "Network Response 500".

Carry code and cause.code through the response, each guarded on
typeof === "string" so no stack, path, or other cause property rides
along, and give ENOTFOUND, ETIMEDOUT, and EAI_AGAIN a message of their
own.
@kmcginnes
kmcginnes force-pushed the proxy-server-errno-in-error-payload branch from 917c01e to c071e6f Compare September 24, 2026 00:04
@kmcginnes
kmcginnes added this pull request to stack #2256 September 24, 2026 00:08
error-handler.test.ts and logging.test.ts each built their own, with the
same app.locals.logger shape. Each request now gets its own logger so a
test can spy on it without leaking onto another test.
node-fetch assigns the errno to code and never sets cause, so the nested
field was unreachable. Resolving code from the error or its cause covers
the same ground with one field, and keeps the nested object out of the
wire format.
ETIMEDOUT shared the DNS message, which told the user to check a
hostname that had already resolved. The name resolved and nothing
answered, so the remedy is a security group, a firewall, or the port.
Both titles are new, and the troubleshooting guide had nothing to find
when a user searched for them. Each gets its cause and its fix, since a
DNS failure and a connect timeout point at different things.
@kmcginnes
kmcginnes marked this pull request as ready for review September 24, 2026 18:06
@kmcginnes
kmcginnes merged commit 3743869 into main Sep 24, 2026
6 checks passed
@kmcginnes
kmcginnes deleted the proxy-server-errno-in-error-payload branch September 24, 2026 18:17
kmcginnes added a commit that referenced this pull request Sep 25, 2026
## Description

A timeout reached the user as one of two things, and neither said who
gave up. The connection's own fetch timeout surfaced as a bare
`DOMException` named `TimeoutError`. A database-side query timeout
surfaced as a `NetworkError` whose body happened to carry
`TimeLimitExceededException`. They have opposite fixes: one is a setting
on the connection, the other is the database's configuration. Anything
that wanted to give specific advice had to sniff error names and body
codes on its own.

This adds two errors, thrown from `fetchDatabaseRequest`, which every
connector (Gremlin, openCypher, SPARQL) already goes through:

- `FetchTimeoutError` carries `timeoutMs`. It's thrown when the fetch
timeout signal fired and the caller's signal didn't, so a user cancel
stays an `AbortError` even if both have fired by the time it's caught.
It now also covers a timeout that fires while the body is being read,
which used to escape as a raw `DOMException`.
- `DatabaseTimeoutError` extends `NetworkError` and carries the
database's `databaseCode`. Staying a `NetworkError` keeps the retry
policy, the error details dialog, and the `requestId` in the details
unchanged. It matches on the body, never on status alone, since Neptune
also returns 500 for memory limits, throttling, and cancellation:
  - Neptune: `code: "TimeLimitExceededException"`
- Gremlin Server: `"Exception-Class":
"java.util.concurrent.TimeoutException"`

`createDisplayError` renders each with its own advice and drops the old
name and code sniffing. The cancellation message no longer claims it
might have been a timeout, since a timeout can't reach that branch any
more. The troubleshooting guide explains the two messages.

Cancelling a Query tab query no longer restores the previous query's
error. `cancelQueries` reverted to the last cached state by default, so
a cancel right after a failure showed that failure again, which with
this change could read "Database query timed out". Cancel now passes
`revert: false`, so the cancel lands on the cancellation branch.

`CONTEXT.md` names the two timeouts, and `docs/agents/connectors.md`
records that `fetchDatabaseRequest` is the only place they are
classified.

`MemoryLimitExceededException` and the `ETIMEDOUT` errno that
[#2249](#2249) now carries are
not query timeouts, and stay plain `NetworkError`s.

Not changed: timeouts are still retried 3 times by the query client.

## Validation

- The Gremlin Server body is a real capture from
`tinkerpop/gremlin-server:3.8`. A request-level `evaluationTimeout`
field is ignored over HTTP, but `g.with("evaluationTimeout", N)` in the
script works, and both the script limit and the server's 30s default
produce the same body. Tests use that exact body.
- The Neptune shape matches the existing `TimeLimitExceededException`
handling.
- `fetchDatabaseRequest` tests cover:
  - the timer firing
- the caller aborting first, then the timeout, and the reverse, with
both signals aborted before the catch runs
- the timeout firing while an error body is read, which keeps the
database's error
  - a timeout during `response.json()`
  - Neptune bodies with and without the proxy's `error` wrapper
- `MemoryLimitExceededException` and `ETIMEDOUT` staying `NetworkError`
- Each explorer has a test for both errors through `rawQuery`.
- Live captures matched the classification on Neptune 1.2.1.0, 1.3.5.0,
1.4.5.1 and 1.4.7.0 (Gremlin), 1.4.5.1 and 1.4.7.0 (SPARQL), and 1.4.7.0
(openCypher), each returning HTTP 500 with `TimeLimitExceededException`,
directly and through the proxy. Earlier versions reject a per-query
openCypher timeout.
- In the browser, both messages showed through the proxy on Gremlin
Server and Neptune, and a cancel with a fetch timeout set stayed a
cancel.
- `pnpm checks` and `pnpm test` are clean: 227 files, 2779 tests.

## Related Issues

None. [#2244](#2244) will
adopt these so edge connection discovery can tell the user which timeout
to raise.

### Check List

- [x] I confirm that my contribution is made under the terms of the
Apache 2.0 license.
- [x] I have verified `pnpm checks` passes with no errors.
- [x] I have verified `pnpm test` passes with no failures.
- [x] I have covered new added functionality with unit tests if
necessary.
- [x] I have updated documentation if necessary.
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