Skip to content

fix(gateway): advertise the host-published gateway port to agent containers - #435

Open
cfis wants to merge 2 commits into
onecli:mainfrom
cfis:fix/advertised-gateway-url-for-offset-stacks
Open

fix(gateway): advertise the host-published gateway port to agent containers#435
cfis wants to merge 2 commits into
onecli:mainfrom
cfis:fix/advertised-gateway-url-for-offset-stacks

Conversation

@cfis

@cfis cfis commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Builds on #227.

Problem

We run one OneCLI stack per user on a single macOS host — each agent in its own Colima Docker VM — with per-user offset ports so they don't collide on 127.0.0.1. #227 made the host ports configurable, but agents in a second, offset stack still can't reach the gateway: every credentialed call from a container dies at the proxy.

Two values ignore the operator's port remapping:

  1. GATEWAY_BASE_URL — the host:port agents put in HTTPS_PROXY — defaults to host.docker.internal:10255 and is never set in docker-compose.yml, so agents dial :10255 no matter what ONECLI_GATEWAY_PORT is.
  2. gateway-invalidate.ts uses GATEWAY_API_URL (the host-published port) for a call that actually runs app→gateway inside the container, where the gateway always listens on 10255.

Both are masked on the default stack (host port == 10255) and under podman host-networking, where the two coincide. They only surface on macOS/Colima bridge networking with offset ports.

Fix

Extends #227 to the values advertised into containers (which #227 deliberately left at the container-internal ports):

  • Wire GATEWAY_BASE_URL to ONECLI_GATEWAY_PORT in docker-compose.yml.
  • Add GATEWAY_SERVICE_URL — the app→gateway URL — pinned to the fixed in-container port, used by gateway-invalidate.ts. It falls back to GATEWAY_API_URL, so single-stack and remote/cloud deployments are unchanged.

I don't love the name GATEWAY_SERVICE_URL, I think it should really be GATEWAY_API_URL but that is already taken. An alternative would be to rename GATEWAY_API_URL to GATEWAY_PUBLIC_URL but I figured that was probably a bridge too far. But let me know and I can update this PR.

Testing

New env.test.ts covers the GATEWAY_SERVICE_URL fallback/override; tsc and eslint clean on the touched files.

🤖 Generated with Claude Code

…ainers

Running two self-hosted stacks on one host (offset ports so they don't
collide) left agent containers unable to reach the gateway: both the proxy
URL handed to containers and the internal cache-invalidation call used a
port that ignored the operator's host-port remapping.

- Wire GATEWAY_BASE_URL (the host:port agents put in HTTPS_PROXY) to
  ONECLI_GATEWAY_PORT in docker-compose, so it tracks the published port
  instead of the hardcoded 10255 default.
- Add GATEWAY_SERVICE_URL: the URL the API uses to reach the gateway for
  server-to-server calls (cache invalidation), pinned to the gateway's
  fixed in-container port. Falls back to GATEWAY_API_URL, so single-stack
  and remote-gateway ("cloud") deployments are unaffected.
- gateway-invalidate.ts uses GATEWAY_SERVICE_URL instead of GATEWAY_API_URL.

GATEWAY_API_URL keeps its meaning (URL advertised to outside callers). The
change is additive and opt-in — behavior only diverges when an operator
sets the new vars for an offset stack.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jul 20, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes two environment-variable mismatches that prevented agent containers in a port-offset self-hosted stack from reaching the gateway: the proxy URL advertised into agent containers (GATEWAY_BASE_URL) was hard-wired to :10255 regardless of ONECLI_GATEWAY_PORT, and the internal cache-invalidation call used the advertised URL (GATEWAY_API_URL) instead of the gateway's fixed in-container address.

  • Wires GATEWAY_BASE_URL to ${ONECLI_GATEWAY_PORT} in docker-compose.yml so agent containers dial the correct published host port.
  • Introduces GATEWAY_SERVICE_URL (defaults to GATEWAY_API_URL) as the server-to-server endpoint for cache invalidation, pinned to the gateway's fixed in-container port in the compose file; gateway-invalidate.ts is updated to use it.
  • Adds env.test.ts covering the fallback and independent-override cases, and registers the new variable in turbo.json.

Confidence Score: 4/5

Safe to merge; the change is additive and the fallback in env.ts ensures existing single-stack and cloud deployments see no difference.

The logic is sound and all existing deployment paths are protected by the GATEWAY_API_URL fallback. The only concern is that GATEWAY_SERVICE_URL in docker-compose.yml is a bare literal rather than a ${VAR:-default} substitution — the pattern every other configurable entry in that file follows — which silently prevents operators from overriding it via the .env file without a compose override file.

docker/docker-compose.yml — the GATEWAY_SERVICE_URL literal vs. substitution pattern is worth a second look before merging.

Important Files Changed

Filename Overview
docker/docker-compose.yml Adds GATEWAY_BASE_URL (using variable substitution) and GATEWAY_SERVICE_URL (hardcoded literal) to the onecli service; the literal breaks the compose file's consistent ${VAR:-default} override pattern.
packages/api/src/lib/env.ts Adds GATEWAY_SERVICE_URL constant that falls back to GATEWAY_API_URL; well-documented and follows existing patterns.
packages/api/src/lib/gateway-invalidate.ts Straightforward swap of GATEWAY_API_URL → GATEWAY_SERVICE_URL in both invalidation call sites; logic is unchanged.
packages/api/src/lib/env.test.ts New test file covering both the fallback behaviour and the independent-override case for GATEWAY_SERVICE_URL; uses vi.stubEnv/resetModules correctly.
turbo.json Adds GATEWAY_SERVICE_URL to globalPassThroughEnv so Turborepo propagates the variable correctly; minimal and correct.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Agent as Agent Container
    participant Host as Host (published port)
    participant App as API Server (container)
    participant GW as Gateway (container, :10255)

    Note over Agent,GW: HTTPS_PROXY set via GATEWAY_BASE_URL
    Agent->>Host: CONNECT host.docker.internal:ONECLI_GATEWAY_PORT
    Host->>GW: forward to :10255 (port mapping)
    GW-->>Agent: proxy tunnel established

    Note over App,GW: Cache invalidation (server-to-server)
    App->>GW: POST 127.0.0.1:10255/v1/cache/invalidate
    Note right of App: GATEWAY_SERVICE_URL (fixed in-container port)
    GW-->>App: 200 OK

    Note over Agent,Host: SDK approval poller / browser
    Agent->>Host: GET GATEWAY_API_URL/v1/...
    Note right of Agent: GATEWAY_API_URL tracks published host port
    Host->>GW: forward
    GW-->>Agent: response
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Agent as Agent Container
    participant Host as Host (published port)
    participant App as API Server (container)
    participant GW as Gateway (container, :10255)

    Note over Agent,GW: HTTPS_PROXY set via GATEWAY_BASE_URL
    Agent->>Host: CONNECT host.docker.internal:ONECLI_GATEWAY_PORT
    Host->>GW: forward to :10255 (port mapping)
    GW-->>Agent: proxy tunnel established

    Note over App,GW: Cache invalidation (server-to-server)
    App->>GW: POST 127.0.0.1:10255/v1/cache/invalidate
    Note right of App: GATEWAY_SERVICE_URL (fixed in-container port)
    GW-->>App: 200 OK

    Note over Agent,Host: SDK approval poller / browser
    Agent->>Host: GET GATEWAY_API_URL/v1/...
    Note right of Agent: GATEWAY_API_URL tracks published host port
    Host->>GW: forward
    GW-->>Agent: response
Loading

Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
docker/docker-compose.yml:50
`GATEWAY_SERVICE_URL` is the only value in the `environment` block that uses a bare literal instead of `${VAR:-default}` substitution. Every other configurable entry (`ONECLI_BIND_HOST`, `ONECLI_APP_PORT`, `ONECLI_GATEWAY_PORT`, etc.) follows the substitution pattern so operators can override via shell or the root `.env` file. With a literal, Docker Compose ignores any `GATEWAY_SERVICE_URL` an operator places in the `.env` file — the hardcoded string always wins — meaning the only escape hatch is a `docker-compose.override.yml`. Keeping the same substitution pattern here preserves the default behaviour (fixed in-container port) while letting advanced operators override without touching the compose file.

```suggestion
      GATEWAY_SERVICE_URL: ${GATEWAY_SERVICE_URL:-http://127.0.0.1:10255}
```

Reviews (1): Last reviewed commit: "fix(gateway): advertise the host-publish..." | Re-trigger Greptile

Comment thread docker/docker-compose.yml
# (cache invalidation). The gateway always listens on 10255 inside this
# container regardless of host port remapping, so this stays fixed and is
# independent of ONECLI_GATEWAY_PORT.
GATEWAY_SERVICE_URL: http://127.0.0.1:10255

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 GATEWAY_SERVICE_URL is the only value in the environment block that uses a bare literal instead of ${VAR:-default} substitution. Every other configurable entry (ONECLI_BIND_HOST, ONECLI_APP_PORT, ONECLI_GATEWAY_PORT, etc.) follows the substitution pattern so operators can override via shell or the root .env file. With a literal, Docker Compose ignores any GATEWAY_SERVICE_URL an operator places in the .env file — the hardcoded string always wins — meaning the only escape hatch is a docker-compose.override.yml. Keeping the same substitution pattern here preserves the default behaviour (fixed in-container port) while letting advanced operators override without touching the compose file.

Suggested change
GATEWAY_SERVICE_URL: http://127.0.0.1:10255
GATEWAY_SERVICE_URL: ${GATEWAY_SERVICE_URL:-http://127.0.0.1:10255}
Prompt To Fix With AI
This is a comment left during a code review.
Path: docker/docker-compose.yml
Line: 50

Comment:
`GATEWAY_SERVICE_URL` is the only value in the `environment` block that uses a bare literal instead of `${VAR:-default}` substitution. Every other configurable entry (`ONECLI_BIND_HOST`, `ONECLI_APP_PORT`, `ONECLI_GATEWAY_PORT`, etc.) follows the substitution pattern so operators can override via shell or the root `.env` file. With a literal, Docker Compose ignores any `GATEWAY_SERVICE_URL` an operator places in the `.env` file — the hardcoded string always wins — meaning the only escape hatch is a `docker-compose.override.yml`. Keeping the same substitution pattern here preserves the default behaviour (fixed in-container port) while letting advanced operators override without touching the compose file.

```suggestion
      GATEWAY_SERVICE_URL: ${GATEWAY_SERVICE_URL:-http://127.0.0.1:10255}
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

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