Skip to content

HTTP client: branch names containing / cause 404 due to unencoded /db/${branch}/… URLs #1320

@rileytomasek

Description

@rileytomasek

Describe the bug

When using the gel HTTP client (via createHttpClient) with a branch/database name that contains a / (for example preview/pr-662 or dev/riley), all queries fail with:

  • ProtocolError: fetch failed with status code 404: Not Found

The same branch works correctly with the gel CLI and other binary-protocol clients. The failure is specific to the HTTP client.

From debugging:

  • The HTTP client constructs URLs for queries using the provided branch/database value directly in the path, e.g. /db/${database}/…, without URL-encoding.
  • If database === "preview/pr-662", the resulting path becomes /db/preview/pr-662/..., which the server interprets as multiple path segments instead of a single database/branch identifier.
  • The Gel API then cannot match the database/branch and returns 404, which the JS client surfaces as a ProtocolError.
  • Changing only the branch name to one without / (e.g. preview-pr-662) makes the HTTP client work again with no other changes.

This effectively means the HTTP client does not support otherwise-valid branch names containing /, while the CLI does.

Reproduction

Minimal reproduction based on the behavior seen in a production app:

  1. In Gel (Cloud or self-hosted), create a branch whose name includes a /:

    gel branch switch -c dev/test-slash --from main --copy-data

    (Any valid name containing / will do; in our real case we used preview/pr-662.)

  2. In an environment that uses the HTTP driver (Node with fetch, Cloudflare Workers, etc.), connect with createHttpClient:

    import { createHttpClient } from 'gel';
    
    const client = createHttpClient({
      instanceName: process.env.GEL_INSTANCE!,   // valid instance
      secretKey: process.env.GEL_SECRET_KEY!,    // valid secret
      branch: 'dev/test-slash',                  // branch name with '/'
    });
    
    await client.querySingle('SELECT 1');
  3. Observe the behavior:

    • The request results in ProtocolError: fetch failed with status code 404: Not Found.
    • If you inspect the outgoing HTTP request, the URL path includes the branch name verbatim, e.g. /db/dev/test-slash/..., splitting on /.
  4. Now change only the branch name to a value without /:

    const client = createHttpClient({
      instanceName: process.env.GEL_INSTANCE!,
      secretKey: process.env.GEL_SECRET_KEY!,
      branch: 'dev-test-slash', // no slash
    });
    • The same query now succeeds.
  5. Using the gel CLI against the /-containing branch (e.g. gel branch switch dev/test-slash and running queries) works as expected, confirming that:

    • The branch exists.
    • The issue is isolated to how the JS HTTP client constructs its URLs.

Expected behavior

  • Branch/database names that are valid in Gel (including names containing /, such as dev/<user> or preview/pr-<number>) should work with the HTTP client in exactly the same way they work with the CLI and binary clients.
  • The HTTP client should treat the branch/database identifier as data, not as raw path syntax. Concretely:
    • When building URLs like /db/${database}/…, it should percent-encode database (and any similar identifiers) with encodeURIComponent or equivalent.
    • With proper encoding, a branch name like preview/pr-662 would be sent as /db/preview%2Fpr-662/..., allowing the server to resolve it correctly.

Versions (please complete the following information):

  • OS:
    • Reproduced in development on macOS (Apple Silicon).
    • Reproduced in CI/preview on Linux (ubuntu-latest) and Cloudflare Workers.
  • Gel server:
    • Managed Gel Cloud instance with branching enabled (preview/dev branches).
  • Gel CLI:
    • gel 2.x (CLI successfully works with /-containing branch names).
  • gel JS client:
    • gel npm package 2.1.1 (via an app-local wrapper around createHttpClient / createClient).
  • Node:
    • Node.js 22.x in application/CI.
  • Deno:
    • Not used.
  • Runtime environments:
    • Cloudflare Workers (with nodejs_compat, using createHttpClient).
    • Node.js with global fetch available.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions