[FEATURE]: Export actual server URL as environment variable #6609

Open
opened 2026-02-16 18:04:44 -05:00 by yindo · 3 comments
Owner

Originally created by @alvinunreal on GitHub (Jan 17, 2026).

Originally assigned to: @thdxr on GitHub.

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

Problem

When OpenCode starts its HTTP server, the actual port used is not easily discoverable by child processes. This creates issues for integrations that need to spawn additional OpenCode instances (e.g., tmux panes, background tasks, CI/CD scripts).

Current Behavior

  1. Server URL discovery relies on ctx.serverUrl, which returns a fallback when no server is running:

    // From server/server.ts
    export function url(): URL {
      return _url ?? new URL("http://localhost:4096")  // Always returns fallback
    }
    
  2. Port auto-fallback is silent:

    // From server/server.ts:2883
    const server = opts.port === 0 
      ? (tryServe(4096) ?? tryServe(0))  // Tries 4096, then random port
      : tryServe(opts.port)
    
    • If port 4096 is taken, OpenCode silently uses a random OS-assigned port
    • Child processes have no way to discover the actual port used
  3. Multi-instance conflicts:

    • User runs OpenCode in Project A → binds to port 4096
    • User runs OpenCode in Project B → falls back to random port (e.g., 49152)
    • Child processes from Project B can't discover port 49152

Real-World Use Case: Tmux Integration

Our plugin (oh-my-opencode-slim) spawns tmux panes that run opencode attach <url> --session <id> to show sub-agent work in real-time.

Current workaround:

  • Require users to configure a fixed port: { "server": { "port": 4096 } }
  • This breaks when multiple OpenCode instances run simultaneously
  • Port conflicts cause startup failures

What we need:

  • Discover the actual server URL dynamically
  • Support multiple concurrent OpenCode instances on different ports

Proposed Solution

Export the actual server URL as an environment variable when the server starts:

// After server starts successfully (server.ts:2886)
const server = opts.port === 0 ? (tryServe(4096) ?? tryServe(0)) : tryServe(opts.port)
if (!server) throw new Error(`Failed to start server on port ${opts.port}`)

_url = server.url

// NEW: Export for child processes
process.env.OPENCODE_SERVER_URL = server.url.toString()

Environment Variables to Export

Variable Example Value Description
OPENCODE_SERVER_URL http://localhost:4096 Full URL of running server
OPENCODE_SERVER_PORT 4096 Port number (optional, for convenience)

Benefits

  1. Child processes can discover server:

    # In tmux pane or background task
    opencode attach $OPENCODE_SERVER_URL --session abc123
    
  2. Multi-instance support:

    • Each OpenCode instance exports its own URL
    • Child processes automatically connect to the correct parent
  3. No breaking changes:

    • Existing code continues to work
    • Environment variable is purely additive
  4. Better than file-based coordination:

    • Environment variables inherit automatically (Unix convention)
    • No cleanup required
    • No file permission issues

Alternative Considered

File-based approach: Write server URL to .opencode/.server-url

  • Pros: Works across all platforms
  • Cons: Requires cleanup, file watching, permission handling, race conditions

Environment variables are cleaner and more Unix-idiomatic.


Implementation Notes

Where to Add This

In packages/opencode/src/server/server.ts after line 2886:

export function listen(opts: { port: number; hostname: string; mdns?: boolean; cors?: string[] }) {
  // ... existing code ...
  
  const server = opts.port === 0 ? (tryServe(4096) ?? tryServe(0)) : tryServe(opts.port)
  if (!server) throw new Error(`Failed to start server on port ${opts.port}`)

  _url = server.url
  
  // Export for child processes
  process.env.OPENCODE_SERVER_URL = server.url.toString()
  process.env.OPENCODE_SERVER_PORT = server.port.toString()

  // ... rest of existing code ...
}

Cleanup on Server Stop

server.stop = async (closeActiveConnections?: boolean) => {
  if (shouldPublishMDNS) MDNS.unpublish()
  
  // Clean up environment variables
  delete process.env.OPENCODE_SERVER_URL
  delete process.env.OPENCODE_SERVER_PORT
  
  return originalStop(closeActiveConnections)
}

Example Usage

Plugin Code (Tmux Integration)

// Before (hardcoded or unreliable fallback)
const serverUrl = ctx.serverUrl?.toString() ?? "http://localhost:4096"

// After (dynamic discovery)
const serverUrl = process.env.OPENCODE_SERVER_URL ?? ctx.serverUrl?.toString()

Shell Scripts

#!/bin/bash
# Spawn a background OpenCode task connected to the parent server
if [ -n "$OPENCODE_SERVER_URL" ]; then
  opencode attach "$OPENCODE_SERVER_URL" --session "$SESSION_ID"
else
  echo "No OpenCode server found"
  exit 1
fi

Related

  • Similar pattern used by tools like DOCKER_HOST, DISPLAY, SSH_AUTH_SOCK
  • Follows Unix convention of using environment variables for service discovery
  • Enables better ecosystem integrations (plugins, scripts, CI/CD)

Would you be open to this enhancement? Happy to submit a PR if this approach makes sense!

Originally created by @alvinunreal on GitHub (Jan 17, 2026). Originally assigned to: @thdxr on GitHub. ### Feature hasn't been suggested before. - [x] I have verified this feature I'm about to request hasn't been suggested before. ### Describe the enhancement you want to request ## Problem When OpenCode starts its HTTP server, the actual port used is not easily discoverable by child processes. This creates issues for integrations that need to spawn additional OpenCode instances (e.g., tmux panes, background tasks, CI/CD scripts). ### Current Behavior 1. **Server URL discovery relies on `ctx.serverUrl`**, which returns a fallback when no server is running: ```typescript // From server/server.ts export function url(): URL { return _url ?? new URL("http://localhost:4096") // Always returns fallback } ``` 2. **Port auto-fallback is silent**: ```typescript // From server/server.ts:2883 const server = opts.port === 0 ? (tryServe(4096) ?? tryServe(0)) // Tries 4096, then random port : tryServe(opts.port) ``` - If port 4096 is taken, OpenCode silently uses a random OS-assigned port - Child processes have no way to discover the actual port used 3. **Multi-instance conflicts**: - User runs OpenCode in Project A → binds to port 4096 - User runs OpenCode in Project B → falls back to random port (e.g., 49152) - Child processes from Project B can't discover port 49152 ### Real-World Use Case: Tmux Integration Our plugin ([oh-my-opencode-slim](https://github.com/anomalyco/oh-my-opencode-slim)) spawns tmux panes that run `opencode attach <url> --session <id>` to show sub-agent work in real-time. **Current workaround:** - Require users to configure a fixed port: `{ "server": { "port": 4096 } }` - This breaks when multiple OpenCode instances run simultaneously - Port conflicts cause startup failures **What we need:** - Discover the actual server URL dynamically - Support multiple concurrent OpenCode instances on different ports --- ## Proposed Solution Export the actual server URL as an environment variable when the server starts: ```typescript // After server starts successfully (server.ts:2886) const server = opts.port === 0 ? (tryServe(4096) ?? tryServe(0)) : tryServe(opts.port) if (!server) throw new Error(`Failed to start server on port ${opts.port}`) _url = server.url // NEW: Export for child processes process.env.OPENCODE_SERVER_URL = server.url.toString() ``` ### Environment Variables to Export | Variable | Example Value | Description | |----------|--------------|-------------| | `OPENCODE_SERVER_URL` | `http://localhost:4096` | Full URL of running server | | `OPENCODE_SERVER_PORT` | `4096` | Port number (optional, for convenience) | ### Benefits 1. **Child processes can discover server**: ```bash # In tmux pane or background task opencode attach $OPENCODE_SERVER_URL --session abc123 ``` 2. **Multi-instance support**: - Each OpenCode instance exports its own URL - Child processes automatically connect to the correct parent 3. **No breaking changes**: - Existing code continues to work - Environment variable is purely additive 4. **Better than file-based coordination**: - Environment variables inherit automatically (Unix convention) - No cleanup required - No file permission issues --- ## Alternative Considered **File-based approach**: Write server URL to `.opencode/.server-url` - **Pros**: Works across all platforms - **Cons**: Requires cleanup, file watching, permission handling, race conditions **Environment variables are cleaner and more Unix-idiomatic.** --- ## Implementation Notes ### Where to Add This In `packages/opencode/src/server/server.ts` after line 2886: ```typescript export function listen(opts: { port: number; hostname: string; mdns?: boolean; cors?: string[] }) { // ... existing code ... const server = opts.port === 0 ? (tryServe(4096) ?? tryServe(0)) : tryServe(opts.port) if (!server) throw new Error(`Failed to start server on port ${opts.port}`) _url = server.url // Export for child processes process.env.OPENCODE_SERVER_URL = server.url.toString() process.env.OPENCODE_SERVER_PORT = server.port.toString() // ... rest of existing code ... } ``` ### Cleanup on Server Stop ```typescript server.stop = async (closeActiveConnections?: boolean) => { if (shouldPublishMDNS) MDNS.unpublish() // Clean up environment variables delete process.env.OPENCODE_SERVER_URL delete process.env.OPENCODE_SERVER_PORT return originalStop(closeActiveConnections) } ``` --- ## Example Usage ### Plugin Code (Tmux Integration) ```typescript // Before (hardcoded or unreliable fallback) const serverUrl = ctx.serverUrl?.toString() ?? "http://localhost:4096" // After (dynamic discovery) const serverUrl = process.env.OPENCODE_SERVER_URL ?? ctx.serverUrl?.toString() ``` ### Shell Scripts ```bash #!/bin/bash # Spawn a background OpenCode task connected to the parent server if [ -n "$OPENCODE_SERVER_URL" ]; then opencode attach "$OPENCODE_SERVER_URL" --session "$SESSION_ID" else echo "No OpenCode server found" exit 1 fi ``` --- ## Related - Similar pattern used by tools like `DOCKER_HOST`, `DISPLAY`, `SSH_AUTH_SOCK` - Follows Unix convention of using environment variables for service discovery - Enables better ecosystem integrations (plugins, scripts, CI/CD) --- Would you be open to this enhancement? Happy to submit a PR if this approach makes sense!
yindo added the discussion label 2026-02-16 18:04:44 -05:00
Author
Owner

@github-actions[bot] commented on GitHub (Jan 17, 2026):

This issue might be a duplicate of existing issues. Please check:

  • #8948: [FEATURE]: Server Registry & Auto-Discovery for Persistent Sessions - Addresses the same core problem of discovering existing OpenCode server instances dynamically, which would enable child processes and persistent session management.

Feel free to ignore if this doesn't address your specific case.

@github-actions[bot] commented on GitHub (Jan 17, 2026): This issue might be a duplicate of existing issues. Please check: - #8948: [FEATURE]: Server Registry & Auto-Discovery for Persistent Sessions - Addresses the same core problem of discovering existing OpenCode server instances dynamically, which would enable child processes and persistent session management. Feel free to ignore if this doesn't address your specific case.
Author
Owner

@alvinunreal commented on GitHub (Jan 17, 2026):

this will allow to write tmux plugin for agents integration

@alvinunreal commented on GitHub (Jan 17, 2026): this will allow to write tmux plugin for agents integration
Author
Owner

@zedd3v commented on GitHub (Jan 29, 2026):

@thdxr @adamdotdevin

@zedd3v commented on GitHub (Jan 29, 2026): @thdxr @adamdotdevin
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#6609