Skip to content

Connect to Postgres by parts, and publish loopback on both addresses - #384

Merged
davidmckayv merged 2 commits into
mainfrom
db-connect-options
Sep 5, 2026
Merged

Connect to Postgres by parts, and publish loopback on both addresses#384
davidmckayv merged 2 commits into
mainfrom
db-connect-options

Conversation

@davidmckayv

Copy link
Copy Markdown
Contributor

Two faults that each stop a deployment reaching its own database. Both found by running the stack on Windows; the second is not Windows-specific.

1. The server cannot reach Postgres on Windows

Bun reads a Postgres URL's path — the database name — as the path of a unix socket, ignores host and port, and fails to open a socket Windows does not have (oven-sh/bun#27713).

It does not look like a parsing bug. On the same machine, at the same moment:

psql inside the container            select 1 -> 1 row
Test-NetConnection 127.0.0.1:5432    TcpTestSucceeded = True
bun, URL form                        FailedToOpenSocket
bun, options form                    OPTIONS FORM OK: [{"ok":1}]

The address now goes in parts.

Passing the parts is not enough on its own. Bun prefers $DATABASE_URL to the options it was handed, so the address goes straight back through the parser this exists to avoid. Observed, not assumed: the options form connects from a Bun script with no $DATABASE_URL set, and still failed inside the server, which starts with --env-file, until the variable was gone. So it is removed from the environment as it is read. loadConfig has already captured it, and the worker reads it into a local before opening a database. That also stops a later bare new SQL() connecting somewhere nobody named.

A URL with no host, or no database, is now refused by name instead of resolving to a default.

2. localhost is not one address

Compose published its loopback ports on 127.0.0.1 only. localhost resolves to ::1 and 127.0.0.1 in an order the platform decides, and Bun and Node fail outright rather than trying the other. So the same configuration works on one machine and not the next, for a reason nothing in the error mentions.

Every loopback port is published on both addresses now. Both are loopback; nothing became reachable from another host.

Verified locally, one Postgres, three clients:

IPv4       127.0.0.1    CONNECTED
IPv6       ::1          CONNECTED
localhost  localhost    CONNECTED

and Compose reports 127.0.0.1:5432->5432/tcp, [::1]:5432->5432/tcp.

Tests

server/tests/db-client-address.test.ts, five cases: the variable is gone after the call, a non-URL is refused, a URL with no host is refused (it parses — the scheme is openbot: — and would connect nowhere), a URL with no database is refused, and the existing wrong-way-round guard still fires. That last one caught a real gap while writing them: my first attempt at "not a URL" was a string new URL happily accepts.

What is still unproven

Fix 1 is the diagnosis of a Windows failure, and the full Windows smoke journey has not yet been run against this branch. That is next, on a bare-metal Windows instance, and I will report the result on this PR rather than merge on the strength of the reasoning.

Two faults that each stop a deployment reaching its own database, found by
running the stack on Windows.

Bun reads a Postgres URL's path, the database name, as the path of a unix
socket, ignoring host and port, so `postgres://user:pass@host:5432/openbot`
cannot connect on Windows at all (oven-sh/bun#27713). It looks like a network
fault: psql inside the container works, and a plain TCP connection from the same
machine works. The address now goes in parts.

Passing the parts is not enough on its own. Bun prefers $DATABASE_URL to the
options it was given, so the address goes straight back through the parser this
avoids. The variable is removed from the environment as it is read; loadConfig
has already captured it and the worker reads it into a local before opening a
database. That also stops a later bare `new SQL()` connecting somewhere nobody
named. A URL with no host, or no database, is refused by name rather than
silently resolving to a default.

Separately, Compose published loopback ports on 127.0.0.1 only, and `localhost`
is not one address: it resolves to ::1 and 127.0.0.1 in an order the platform
decides, and Bun and Node both fail outright rather than trying the other. The
same configuration therefore worked on one machine and not the next. Every
loopback port is published on both now, which is still loopback.

Verified locally: with both published, IPv4, IPv6 and `localhost` all connect.
@davidmckayv davidmckayv added the full-ci Run the slow image builds on this PR label Sep 5, 2026
@davidmckayv

Copy link
Copy Markdown
Contributor Author

Verified on Windows: 5 pass, 0 fail

Windows Server 2022, EC2 bare metal, rootless Podman on WSL2, this branch at bf11f62, real Intelligence credentials.

(pass) a deployment that is up > reports the runtime it is actually running
(pass) a deployment that is up > holds a licence the runtime accepts, and has Bots registered
(pass) a deployment that is up > mints thread ids that say which deployment they came from
(pass) a Bot acting on its computer > reaches a page through the gateway, and the trail records it
(pass) a Bot acting on its computer > is refused by a boundary, and the refusal is recorded with its rule

 5 pass
 0 fail
smoke exit: 0

openbot-computer-risk-analyst Up 3 seconds — the supervisor built the Bot its own computer through the rootless Podman socket, and the gateway test drove Chromium in it.

Both fixes were exercised, not sidestepped

DATABASE_URL was deliberately left as postgres://openbot:openbot@localhost:5432/openbot for this run. localhost, not an explicit IP, so the dual-stack change had to carry it and the URL had to survive the parser.

Compose published both addresses, as intended:

openbot-postgres-1         127.0.0.1:5432->5432/tcp, [::1]:5432->5432/tcp
openbot-agent-computer-1   127.0.0.1:4100->4100/tcp, [::1]:4100->4100/tcp
openbot-agent-bot-1        127.0.0.1:4200->4200/tcp, [::1]:4200->4200/tcp
openbot-agent-langgraph-1  127.0.0.1:4201->4201/tcp, [::1]:4201->4201/tcp
openbot-supervisor-1       127.0.0.1:4500->4300/tcp, [::1]:4500->4300/tcp

The script asserted both changes were present in the checkout before running, so a green result cannot come from a stale tree.

Before this branch

Same machine, same everything, main:

error: FailedToOpenSocket failed to connect to postgresql

while psql inside the container returned a row and Test-NetConnection 127.0.0.1:5432 reported TcpTestSucceeded = True. The server could not reach its own database on Windows at all.

Two failures on the way that were my harness, not the code

Recorded so nobody re-diagnoses them: I first ran the server without COMPUTER_SUPERVISOR_URL, which leaves it in shared-computer mode and makes the two per-Bot tests fail with there is no such bot; and I skipped the reboot after enabling the WSL feature, which makes podman machine init fail with WSL_E_WSL_OPTIONAL_COMPONENT_REQUIRED. Neither is a defect in this branch.

Taking the address apart dropped everything after the '?'. The profile store's
serialization tests set ?application_name= and then look for that session in
pg_stat_activity, so losing it turned two lock tests into three second timeouts
with nothing in the failure to say why. Bun takes these as `connection`.

Anything else Postgres accepts on a URL travels the same way, sslmode included,
rather than only the parameter that happened to be caught.
@davidmckayv
davidmckayv merged commit 1756781 into main Sep 5, 2026
14 checks passed
@davidmckayv
davidmckayv deleted the db-connect-options branch September 5, 2026 19:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

full-ci Run the slow image builds on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant