Skip to content

Add --root-path option for reverse proxy support#1

Merged
DaehoYang merged 7 commits intodevfrom
copilot/add-root-path-option
Feb 1, 2026
Merged

Add --root-path option for reverse proxy support#1
DaehoYang merged 7 commits intodevfrom
copilot/add-root-path-option

Conversation

Copy link
Copy Markdown

Copilot AI commented Feb 1, 2026

What does this PR do?

Adds --root-path CLI option to opencode web and opencode serve for deployment behind reverse proxies (e.g., Jupyter Server Proxy).

Implementation:

  • CLI/Config: Added rootPath string option with validation (must start with /). Supports both CLI arg and config file (server.rootPath).

  • Server: Uses Hono's basePath() to prefix all routes including WebSocket upgrades:

    const baseApp = opts.rootPath 
      ? new Hono().basePath(opts.rootPath).route("/", App())
      : App()
  • URL Construction: Uses URL constructor throughout for safe handling of special characters and encoding.

  • Display: Updated web and serve commands to show correct URLs with path prefix.

Usage:

opencode serve --root-path /jupyter/proxy/opencode

Or via config:

{
  "server": {
    "rootPath": "/proxy"
  }
}

Backward compatible—empty or omitted rootPath maintains existing behavior.

How did you verify your code works?

  • Created comprehensive integration tests covering URL construction, validation, basePath logic, and edge cases (special characters, multiple segments)
  • All 12 tests pass (100% success rate)
  • CodeQL security scan: 0 alerts
  • Verified URL encoding prevents injection attacks
  • Validated path rejection with clear error messages showing invalid values
Original prompt

Add --root-path option for reverse proxy support

Overview

Add --root-path option to opencode web and opencode serve commands to support running behind reverse proxies like Jupyter Server Proxy. This option adds a base path prefix to all HTTP routes.

Changes Required

1. CLI Option Definition

File: packages/opencode/src/cli/network.ts

Add rootPath option to the options object:

const options = {
  port: { ... },
  hostname: { ... },
  mdns: { ... },
  cors: { ... },
  rootPath: {
    type: "string" as const,
    describe: "base path for reverse proxy",
    default: "",
  },
}

Update resolveNetworkOptions function to handle rootPath from config and CLI:

const rootPath = rootPathExplicitlySet 
  ? args.rootPath 
  : (config?.server?.rootPath ?? args.rootPath)

return { hostname, port, mdns, cors, rootPath }

2. Config Schema Update

File: packages/opencode/src/config/config.ts

Add rootPath field to the Server schema (around line 850-860):

export const Server = z
  .object({
    port: z.number().int().positive().optional().describe("Port to listen on"),
    hostname: z.string().optional().describe("Hostname to listen on"),
    mdns: z.boolean().optional().describe("Enable mDNS service discovery"),
    cors: z.array(z.string()).optional().describe("Additional domains to allow for CORS"),
    rootPath: z.string().optional().describe("Base path for reverse proxy"),
  })
  .strict()
  .meta({
    ref: "ServerConfig",
  })

3. Server Implementation

File: packages/opencode/src/server/server.ts

Modify the listen function signature to accept rootPath:

export function listen(opts: { 
  port: number; 
  hostname: string; 
  mdns?: boolean; 
  cors?: string[];
  rootPath?: string;
})

Apply basePath when rootPath is provided (around line 567-580):

const baseApp = opts.rootPath 
  ? new Hono().basePath(opts.rootPath).route("/", App())
  : App();

const args = {
  hostname: opts.hostname,
  idleTimeout: 0,
  fetch: baseApp.fetch,
  websocket: websocket,
} as const

Update the URL construction to include rootPath:

_url = opts.rootPath 
  ? new URL(`${server.url.origin}${opts.rootPath}`)
  : server.url

4. Web Command Update

File: packages/opencode/src/cli/cmd/web.ts

Update URL display to include rootPath (around line 47-76):

const baseUrl = opts.rootPath 
  ? `http://localhost:${server.port}${opts.rootPath}`
  : `http://localhost:${server.port}`

UI.println(UI.Style.TEXT_INFO_BOLD + "  Local access:      ", UI.Style.TEXT_NORMAL, baseUrl)

Apply the same logic for network IPs display.

5. Serve Command Update

File: packages/opencode/src/cli/cmd/serve.ts

Update console output (around line 14-16):

const displayUrl = opts.rootPath
  ? `http://${server.hostname}:${server.port}${opts.rootPath}`
  : `http://${server.hostname}:${server.port}`
console.log(`opencode server listening on ${displayUrl}`)

6. Documentation Updates

File: packages/web/src/content/docs/cli.mdx

Add --root-path to both web and serve command flags tables (around lines 365 and 461):

| `--root-path` | Base path for reverse proxy                      |

File: packages/web/src/content/docs/server.mdx

Update options table (around line 13-27):

| `--root-path` | Base path for reverse proxy                    | (empty)     |

Add usage example:

opencode serve --root-path /jupyter/proxy/opencode

File: packages/web/src/content/docs/config.mdx

Update server configuration section (around line 184-203):

- `rootPath` - Base path for reverse proxy. All routes will be prefixed with this path.

Add example:

{
  "$schema": "https://opencode.ai/config.json",
  "server": {
    "port": 4096,
    "hostname": "0.0.0.0",
    "rootPath": "/proxy"
  }
}

Testing Instructions

Manual Testing

# Test with root path
opencode web --root-path /proxy --port 4096

# Verify these URLs work correctly:
# - http://localhost:4096/proxy (main interface)
# - http://localhost:4096/proxy/global/health (API endpoint)
# - Verify all static assets load correctly
# - Verify WebSocket connections work

Expected Behavior

  1. Server starts with rootPath prefix
  2. All routes are accessible under /proxy prefix
  3. Static assets and WebSocket connections work correctly
  4. URL displayed in console includes the rootPath
  5. Browser automatically opens to the correct URL with prefix

Implementation Notes

  • Hono's basePath() automatically handles all route prefixing including WebSocket upgrades
  • No changes needed to individual route handlers
  • The _url variable should store the full URL including rootPath for correct behavior across the codebase
  • Ensure backward compatibility: when rootPath is empty or not provided, behavior remains unchanged

This pull request was created from Copilot chat.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 5 commits February 1, 2026 22:26
Co-authored-by: DaehoYang <129835752+DaehoYang@users.noreply.github.com>
Co-authored-by: DaehoYang <129835752+DaehoYang@users.noreply.github.com>
Co-authored-by: DaehoYang <129835752+DaehoYang@users.noreply.github.com>
Co-authored-by: DaehoYang <129835752+DaehoYang@users.noreply.github.com>
Co-authored-by: DaehoYang <129835752+DaehoYang@users.noreply.github.com>
Copilot AI changed the title [WIP] Add --root-path option for reverse proxy support Add --root-path option for reverse proxy support Feb 1, 2026
Copilot AI requested a review from DaehoYang February 1, 2026 22:34
Co-authored-by: DaehoYang <129835752+DaehoYang@users.noreply.github.com>
@DaehoYang DaehoYang marked this pull request as ready for review February 1, 2026 22:45
@DaehoYang DaehoYang merged commit 85b6f0c into dev Feb 1, 2026
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.

2 participants