Skip to content

OpenCode satellite: preserve manual config, fix LiteLLM auth, add workspace init hook #250

Description

@brandon-nuttall

Workflow

Harbor can run OpenCode as an always-on satellite service alongside LiteLLM. An external orchestrator (another agent, a cron job, a CI pipeline) dispatches coding tasks to the satellite via docker exec harbor.opencode opencode run --model <provider>/<model> --dir <workspace> "...". The satellite connects to LiteLLM for inference, implements the task in a git workspace, and pushes a branch. The orchestrator then opens a PR, dispatches a review (optionally to a different model via the same satellite), and merges.

This enables fully autonomous development loops with no human at a terminal.

Problem

Three issues in services/compose.opencode.ts prevent this workflow from working reliably:

1. Discovery script overwrites manual config on every start

The script unconditionally writes opencode.json on container startup, destroying any manual configuration. Users who need custom provider names, model aliases, or per-model limits lose their config on every harbor up. The OPENCODE_CONFIG_CONTENT env var in override.env was intended to let users provide custom config, but the discovery script overwrites the file regardless.

2. LiteLLM authentication failure

The resolve_key() function defaults to sk-harbor for all backends. LiteLLM uses LITELLM_MASTER_KEY (defaults to sk-litellm). When the discovery script queries http://litellm:4000/v1/models with sk-harbor, it gets a 401, an empty model list, and writes a config with zero providers. The container starts but opencode run fails with ProviderModelNotFoundError.

3. No workspace init hook

When the container is recreated, workspace state (cloned repos, git config, installed tools) is lost. There's no mechanism to run a startup script from the persistent config volume to restore workspace state.

Reproduction

harbor up litellm opencode

# Manually configure a custom provider
docker exec harbor.opencode bash -c 'cat > /root/.config/opencode/opencode.json << EOF
{"$schema":"https://opencode.ai/config.json","provider":{"my-provider":{"npm":"@ai-sdk/openai-compatible","name":"My Provider","models":{"some-model":{}},"options":{"baseURL":"http://litellm:4000/v1","apiKey":"sk-litellm"}}}}
EOF'

# Restart — config is gone, replaced by empty discovery result
harbor down opencode && harbor up opencode

docker exec harbor.opencode cat /root/.config/opencode/opencode.json
# Output: discovery-generated config with provider "harbor-litellm" but zero models (401 auth)

Proposed fix

Three changes to generateDiscoveryScript() in services/compose.opencode.ts:

1. Skip discovery if config already exists:

if [ -s "$$CONFIG_FILE" ]; then
  echo "Config file exists at $$CONFIG_FILE, skipping model discovery"
  exec opencode serve --hostname=0.0.0.0 --port=4096
fi

2. Resolve LiteLLM key from env:

resolve_key() {
  name="$$1"
  case "$$name" in
    litellm) [ -n "$$LITELLM_MASTER_KEY" ] && printf '%s' "$$LITELLM_MASTER_KEY" && return 0 ;;
  esac
  # ... existing sidecar key file logic ...
}

3. Run workspace init script from config volume:

# At the top of the discovery script, after harbor CLI init
if [ -x /root/.config/opencode/init.sh ]; then
  /root/.config/opencode/init.sh || echo "workspace init failed; continuing"
fi

This lets users drop an init.sh on the persistent config volume (~/.harbor/opencode/config/init.sh) to clone repos, configure git, install tools, etc. — surviving container recreations.

Impact

Without these fixes, using OpenCode as a satellite with LiteLLM requires manually patching compose.opencode.ts after every Harbor update, and the container can't survive a restart without losing its config and workspace.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions