Repository navigation
[FEATURE]: Export actual server URL as environment variable #9099
Description
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
-
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 }
-
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
-
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
-
Child processes can discover server:
# In tmux pane or background task opencode attach $OPENCODE_SERVER_URL --session abc123
-
Multi-instance support:
- Each OpenCode instance exports its own URL
- Child processes automatically connect to the correct parent
-
No breaking changes:
- Existing code continues to work
- Environment variable is purely additive
-
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
fiRelated
- 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!
Activity
github-actions commentedon Jan 17, 2026
This issue might be a duplicate of existing issues. Please check:
Feel free to ignore if this doesn't address your specific case.
alvinunreal commentedon Jan 17, 2026
this will allow to write tmux plugin for agents integration
zedd3v commentedon Jan 29, 2026
@thdxr @adamdotdevin
alvinunreal commentedon Feb 24, 2026
@thdxr
gustavocaiano commentedon Apr 2, 2026
Hello @alvinunreal im having an issue where everytime Orchestrator delegates to a sub agent, a new pane is created (cmux) that always shows this :
is this anyhow linked to this issue ?
it is automatic and closes when the sub agent finishes (if i dont close it first)
fix: resolve serverUrl lazily to get the actual server port
fix: discover actual server port for subagent attach
fzacek commentedon Apr 9, 2026
@gustavocaiano : are you using opencode-cmux? because this issue was addressed in version 0.2.2
Also be careful about the plugin management when you update because the doc page is apparently out of date.
Another thing somewhat related to the port thing is that the CLI help, for the
--portoption states it defaults to 0.Which is simply not true. Without the option, there is no server. To actually have opencode pick a random port, you must explicitly run it with
--port 0.This leads me to my last point and that is that it probably would be nice if "starting a server" was available through config or at least env variable. Because aliasing just to force a parameter is not ideal.
gustavocaiano commentedon Apr 9, 2026
@fzacek thank you so much ! yea i figured it out yesterday and now have opencode-cmux updated.
i had to alias :
this way opencode is set to run with
--port 0but opencode attach doesnt run with --port 0 when delegating a sub agent.let me know if you found a better solution !
Zireael commentedon Jun 21, 2026
Seems straightforward improvement. Why is it open for 6 months?
alvinunreal commentedon Jun 21, 2026
I got married meanwhile
github-actions commentedon Aug 21, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.