Skip to content

[Bug] getConnectionOptionsFromUri breaks Postgres Unix-socket URLs (percent-encoded host and ?host= query-param forms) #283

@ujvk

Description

@ujvk

Describe the bug

DatabaseSessionService's connection-string parser (getConnectionOptionsFromUri in core/src/sessions/db/operations.ts) is incompatible with both of the standard Postgres URL forms used to connect to a Cloud SQL instance over a Unix-domain socket. The result is that adk api_server --session_service_uri=... can't be pointed at a Cloud SQL–backed Postgres from inside Cloud Run or App Engine, even though the exact same connection string works with pg/knex when used directly.

The parser does:

const { host, port, username, password, pathname } = new URL(uri);
const hostName = host.split(":")[0];
// ...
return { host: hostName, port, user: username, password, dbName: pathname.slice(1), driver, entities };

It only reads the URL's authority, never looks at URL.search, and never decodes percent-encoding. That breaks two forms, in two different ways:

Form 1 — query-parameter host (pg's alternate URL form for sockets):

postgresql://user:pass@/dbname?host=/cloudsql/my-project:us-central1:my-instance

This fails even earlier. Node's WHATWG URL implementation throws on the empty authority between @ and /:

TypeError [ERR_INVALID_URL]
  input: 'postgresql://user:pass@/dbname?host=/cloudsql/my-project:us-central1:my-instance'

so new URL(uri) throws before getConnectionOptionsFromUri can return. And even if the parse succeeded, the function never consults URL.search, so the host=… query parameter is silently dropped. This is likely the same root cause as #279 (?sslmode= and other query-string options ignored), generalized.

Form 2 — percent-encoded Unix-socket host (pg's primary URL form for sockets):

postgresql://user:pass@%2Fcloudsql%2Fmy-project%3Aus-central1%3Amy-instance/dbname

new URL() parses this, but because postgresql is a non-special scheme the WHATWG URL parser preserves percent-encoding inside URL.host. The parser then takes host.split(':')[0] verbatim — %2Fcloudsql%2Fmy-project%3Aus-central1%3Amy-instance — and passes it as host: to MikroORM's PostgreSqlDriver. The driver (via knex → pg) treats it as a TCP hostname and pg.Client.connect() calls net.createConnection({ host, port: 5432 }), which fails:

Failed to create session: Error: getaddrinfo EAI_AGAIN %2Fcloudsql%2Fmy-project%3Aus-central1%3Amy-instance

pg itself supports this URL form via its own connection-string parser, but the ADK pre-decomposes the URL and never hands pg the original string, so that support is bypassed.

To Reproduce

  1. Provision any Cloud SQL Postgres instance and attach it to a Cloud Run service via gcloud run services update ... --add-cloudsql-instances=<instance-connection-name> so the Unix socket is mounted at /cloudsql/<instance-connection-name>/.s.PGSQL.5432.
  2. Install @google/adk and @google/adk-devtools.
  3. Start the api_server inside the container with a Unix-socket session URI, e.g.:
    npx adk api_server /app/agents/myAgent \
      --port=8000 --host=0.0.0.0 \
      --session_service_uri='postgresql://user:pass@%2Fcloudsql%2Fmy-project%3Aus-central1%3Amy-instance/mydb'
  4. Issue any session create: curl -XPOST https://<service>/apps/myAgent/users/u1/sessions/s1.
  5. Observe:
    ERROR: [ADK API Server] Failed to create session:
    Error: getaddrinfo EAI_AGAIN %2Fcloudsql%2Fmy-project%3Aus-central1%3Amy-instance
    
  6. Re-run with Form 2 (--session_service_uri='postgresql://user:pass@/mydb?host=/cloudsql/my-project:us-central1:my-instance'). The server fails at startup with TypeError [ERR_INVALID_URL].

Both forms work as expected when passed directly to pg:

import pg from 'pg';
const c = new pg.Client('postgresql://user:pass@%2Fcloudsql%2Fmy-project%3Aus-central1%3Amy-instance/mydb');
await c.connect(); // succeeds

Expected behavior

Standard Postgres Unix-socket connection strings should work with --session_service_uri, matching the behavior of pg/knex/libpq. Concretely, both forms below should connect successfully:

  • postgresql://user:pass@%2Fcloudsql%2F<instance>/<db>
  • postgresql://user:pass@/<db>?host=/cloudsql/<instance>

Additional context

  • Deployment shape: Cloud Run service with --add-cloudsql-instances=<INSTANCE_CONNECTION_NAME> (the Google-recommended way to talk to Cloud SQL from Cloud Run without a sidecar). Unix socket appears at /cloudsql/<INSTANCE_CONNECTION_NAME>/.s.PGSQL.5432; this is the standard path documented at https://cloud.google.com/sql/docs/postgres/connect-run#connect-built-in.
  • Separately, noticed while digging: core/src/cli/cli.ts defines the option with the flag string and description merged into a single argument:
    new Option(
      "--session_service_uri <string>, Optional. The URI of the session service. Supported URIs: memory:// for in-memory session service."
    );
    vs. the adjacent --log_level which uses the documented two-argument form. Commander 14 is permissive enough that this still parses correctly today, but it's a latent bug in the same file — any future Commander tightening on flag-string validation would break --session_service_uri parsing entirely. Probably worth fixing in the same PR since anyone touching this area will be reading cli.ts anyway. Same applies to --artifact_service_uri right below.

Metadata

Metadata

Assignees

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