Skip to content

refactor(registry/coder-labs/modules/opencode)!: remove agentapi, tasks and start logic - #1159

Open
35C4n0r wants to merge 13 commits into
mainfrom
35C4n0r/migrate-opencode
Open

35C4n0r wants to merge 13 commits into
mainfrom
35C4n0r/migrate-opencode

Conversation

@35C4n0r

@35C4n0r 35C4n0r commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Closes REG-85

Refactor coder-labs/opencode to install and configure OpenCode 2 only, matching the codex, copilot, and cursor-cli (#1152) migrations. Targets the OpenCode V2 docs.

  • Replace the agentapi module with coder-utils; drop Tasks inputs (order, group, report_tasks, cli_app, web_app_display_name, cli_app_display_name, subdomain, install_agentapi, agentapi_version, ai_prompt), the start script, the task-reporting coder MCP server, and the task_app_id output.
  • Install OpenCode 2 via the official V2 installer (https://opencode.ai/v2/install, temp file + bash -n validation, retrying curl, --no-modify-path). The installer run itself is retried up to 3 times because its latest-version lookup intermittently returns 500. latest is passed as an empty VERSION. Like the other agent modules, the installer runs on every start when install_opencode = true; no installed-version check. opencode_version is base64-encoded into the script.
  • Make workdir optional (created if missing). OpenCode has no trust prompt.
  • Rename config_json to base_opencode_config. When set it overwrites the global opencode.json ($OPENCODE_CONFIG_DIR, else $XDG_CONFIG_HOME/opencode, else ~/.config/opencode) on every start.
  • New mcp variable (server map) added under V2's mcp.servers after the base config. Servers already in the file win on duplicate names, like claude mcp add (verified on Claude Code 2.1.296: a duplicate name exits 1 and keeps the stored server). If the file is JSONC or invalid JSON, mcp is skipped with a warning.
  • auth_json takes the JSON array printed by opencode auth export and is applied with opencode auth import --standalone. Delivered through coder_env CODER_OPENCODE_AUTH_JSON (never rendered into the script). The module's credential ids are logged out first so rotated secrets apply; other saved accounts are preserved.
  • Remove continue and session_id: they only fed the launch command, so they become --continue / --session flags in the user's coder_app.
  • No input validation blocks on the variables.
  • Add scripts output; README reduced to examples and doc links; rewrite tests. Bump to 1.0.0.

Important

Worth a reviewer check:

  • opencode mcp add is an upsert, so "existing servers win" deviates from native semantics. Kept for consistency with the sibling modules and to avoid clobbering in-workspace edits; template changes to an existing server name do not propagate.
  • auth_json is exported workspace-wide as CODER_OPENCODE_AUTH_JSON, visible to every workspace process (same exposure as provider API key env vars).
  • OAuth entries in auth_json are passed through to auth import unchanged but were not exercised; only API-key credentials were tested.
Decision log

Verified against the installed CLI (opencode v2.0.27) and the V2 docs:

Skill pattern OpenCode 2 reality Decision
Pass version straight to official installer V2 installer reads VERSION/--version; latest must be empty; latest lookup (opencode.ai/update/api/latest/cli/npm) intermittently 500s Map latest to empty; retry installer run 3x; base64-encode the version
Installer handles PATH Installs to ~/.opencode/bin (plus an opencode2 shim) --no-modify-path; module links into CODER_SCRIPT_BIN_DIR and updates profiles
Model via documented env var No model env var; model config key or agent/command model refs No model variable; users pick the model in OpenCode
Single tool-specific token env var Credentials per provider; V2 stores them in SQLite. Legacy auth.json is only imported by a migration that a fresh DB skips (schema bootstrap) auth_json = auth export array, applied via auth import; provider env vars documented as alternative
managed_settings root-owned policy V2 does not load /etc/opencode/opencode.json (absent from debug config sources and the binary). Policies come from config (experimental.policies) or OpenCode Console Removed managed_settings; no config input remains, so policies are left to the user or OpenCode Console
MCP merge, existing wins V2 nests servers under mcp.servers, uses disabled; V1 top-level entries still normalized; enabled inside servers is dropped Write mcp.servers; existing wins, matching claude mcp add-json
Config file location OPENCODE_CONFIG_DIR > XDG_CONFIG_HOME/opencode > ~/.config/opencode (opencode debug paths config) Script resolves the same way
Config takes effect Shared background service caches config until opencode reload Not handled by the module; at workspace start the service is normally not running yet
Workdir trust key No trust concept Only create the workdir
AI Gateway port Not present pre-migration Out of scope
Launch-only flags (continue, session_id) --continue, --session, --prompt, --auto CLI flags Removed; documented for the coder_app

Testing

  • terraform fmt, terraform validate, terraform test (7 passed)
  • bun test main.test.ts (15 passed, container-based)
  • Real V2 install of the rendered script in codercom/enterprise-node:latest (opencode v2.0.27): user's existing MCP server kept with a warning, other keys untouched, V1-style server kept and new server added under mcp.servers (confirmed via opencode debug config); credential imported, rotated on rerun, a separate user account preserved, secret absent from the log
  • Dogfood workspace from a test template (OpenCode 2.0.28): script order with a downstream coder exp sync script, MCP merge (user server kept), everything MCP server connected and its tool used in opencode run, model reply through Coder AI Gateway, credential import and rotation, restart, pinned 2.0.27 downgrade
  • shellcheck on the rendered install script and mock, prettier, readmevalidation; all v2 docs links return 200

Generated with Coder Agents.

…ks and start logic

- Replace the agentapi module with coder-utils and an install-only
  scripts/install.sh.tftpl using the official installer.
- Drop Tasks/AgentAPI inputs, the start script, the task-reporting MCP
  server, and the task_app_id output; add the scripts output.
- Make workdir optional; merge config_json and the new mcp variable into
  ~/.config/opencode/opencode.json (existing MCP servers win).
- Deliver auth_json via coder_env and merge it into auth.json (0600).
- Add managed_settings written as root to /etc/opencode/opencode.json.
- Rewrite README and tests; bump to 1.0.0.
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Module Scorecard Check

coder-labs/opencode: first scorecard, 67 / 100

No specific score is required to contribute, but modules with higher scores are more likely to be approved by the Coder team and widely used.

Full scorecard for this PR
Presentation & Onboarding Agent Integration Credential Hygiene Restricted-Environment Readiness Engineering Quality Overall
12 / 17 12.5 / 25 18 / 20 12 / 20 7 / 10 67 / 100
Drilldown

Presentation & Onboarding — 12 / 17

Criterion Max Score Notes
Configuration-mode examples 12 12 README documents major modes: basic install, standalone with coder_app launcher (workdir + auth), base_opencode_config + mcp configuration, pre/post install scripts, and auth setup. Each major option (install_opencode, workdir, auth_json, base_opencode_config, mcp, opencode_version) has a documented example or clear variable description with sensible defaults.
Visual preview 5 0 No embedded image, GIF, or video in the README. The frontmatter references an SVG icon file, but no visual demonstration of the module in action is present.

Agent Integration — 12.5 / 25

Criterion Max Score Notes
AI governance 10 0 No mention of Coder AI Gateway or Agent Firewall anywhere in the README. The WARNING explicitly states v1 "drops support for Coder Tasks and AgentAPI." No governance mechanism is documented.
Dashboard entry point 5 5 README "Standalone mode with a launcher app" example provides a complete coder_app resource with slug, display_name, icon, open_in = "slim-window", and a launch command. The WARNING directs users to "launch it with your own coder_app," and the example fulfills this.
Session continuity 5 2.5 The coder_app example uses exec opencode --continue, which is OpenCode's native session-resume flag. This is documented in the README example but is not a module-managed feature—no session ID tracking, persistent session manager (tmux/boo/screen), or reconnect logic is provided by the module itself.
Managed configuration 5 5 base_opencode_config overwrites the global opencode.json on every start; mcp writes under mcp.servers with merge semantics (existing servers win). Both are documented in the README with JSON examples and linked to OpenCode config docs.

Credential Hygiene — 18 / 20

Criterion Max Score Notes
Secrets marked sensitive 16 16 auth_json is declared sensitive = true. The README example uses ${var.anthropic_api_key} (a Terraform interpolation, not a literal key). The install script reads credentials from CODER_OPENCODE_AUTH_JSON env var (delivered via coder_env), never rendering them into the script body. Tests explicitly assert the secret does not appear in the script or logs.
Non-hardcoded auth path 4 2 The README shows credentials arriving via a Terraform variable (var.anthropic_api_key) passed through coder_env. This avoids hardcoding keys in the module template, but does not demonstrate an external auth mechanism (ServiceAccount, IAM/OAuth, API key helper, or AI Gateway). Partial credit for the variable-based path.

Restricted-Environment Readiness — 12 / 20

Criterion Max Score Notes
Mirrorable artifact source 5 0 The install script hardcodes https://opencode.ai/v2/install as the download URL. No module input variable overrides this URL. opencode_version controls the version tag passed to the installer, not the source URL. No variable exists to point to an internal mirror.
Bring-your-own binary 10 10 install_opencode (default true) can be set to false to skip installation entirely. The install script then validates that a working opencode binary exists on PATH and exits with an error if not. Documented in the variable description: "When false, a working OpenCode 2 opencode must already be on PATH."
Egress transparency 3 0 No dedicated README section enumerates external endpoints. The only external URL (https://opencode.ai/v2/install) is visible only in the install script source. No "Network Requirements," "Offline," or "Air-gapped" section exists.
Runs without sudo 2 2 The install script never invokes sudo. All operations target user-space paths: $HOME/.opencode/bin, $HOME/.config/opencode/, and shell profile files in $HOME/. No system-level writes or privilege escalation.

Engineering Quality — 7 / 10

Criterion Max Score Notes
Input quality 6 3 All 10 variables have clear descriptions and sensible defaults. However, no variable has a validation block. agent_id (non-empty string), workdir (absolute path), and opencode_version (semver or "latest") would benefit from validation. Descriptions and defaults are strong, but the absence of any validation is a gap.
Test coverage 4 4 main.tftest.hcl covers Terraform business logic: defaults, workdir trimming, auth env-var creation, MCP encoding, version pinning, and scripts output ordering. main.test.ts provides 14 end-to-end tests executing actual scripts in containers: happy path, install-disabled validation, official installer URL/retry/timeout, version passing, transient failure retry, terminal download failure, workdir creation, config-dir env, MCP write/merge/JSONC handling, auth import/rotation/leak prevention, and pre/post scripts.

Overall — 67 / 100

Raw 61.5 / 92 → round(61.5 / 92 × 100) = 67

Tip

You can run this locally by telling your agent: "review this module against .github/scorecard/SCORECARD.md".


Scored against SCORECARD.md with solstice-1. Language-model scores are advisory.

- Install with the V2 installer (opencode.ai/v2/install), retry transient
  failures, and upgrade or reject OpenCode 1.
- auth_json now takes `opencode auth export` output and is applied with
  `opencode auth import`; V2 ignores auth.json on fresh installs.
- Write mcp under mcp.servers and reject the V1 enabled field.
- Drop managed_settings; V2 does not read /etc/opencode. Document
  experimental.policies instead.
- Respect OPENCODE_CONFIG_DIR/XDG_CONFIG_HOME and reload a running
  shared service after config changes.
- Point docs links at the V2 docs.
@matifali

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T07:29:01.556750Z 83e4518 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 83e4518bed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

existing_ids=$(opencode auth export --standalone 2> /dev/null | jq -r '.[].id' 2> /dev/null || true)
while IFS=$'\t' read -r integration credential_id; do
if [ -n "$${credential_id}" ] && grep -qxF "$${credential_id}" <<< "$${existing_ids}"; then
opencode auth logout --standalone "$${integration}" "$${credential_id}" < /dev/null > /dev/null 2>&1 || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Import replacements before deleting saved credentials

When auth_json rotates an existing ID and the subsequent import fails—for example because a credential value has an invalid shape that the Terraform validation currently accepts—this logs out the working credential before reporting the import error, leaving the workspace without either credential. OpenCode documents that auth logout removes a saved account, so preserve/export the old credential until the replacement succeeds or restore it on failure.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not changed, intentionally. opencode auth import skips ids that already exist, so the module logs out its own credential ids first; otherwise rotated secrets would never apply. Only ids listed in auth_json are removed and other saved accounts are untouched. If the import fails, startup fails with an explicit error, and the next start with a corrected auth_json imports it again. Verified in a dogfood workspace: changing the key left exactly one stored credential with the new value.

Generated with Coder Agents.

Comment on lines +87 to +89
is_opencode_v1() {
[[ "$1" =~ ^1\. ]]
}

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 Badge Reject legacy 0.x binaries as non-v2

This recognizes only 1.x as incompatible, so an existing legacy 0.x OpenCode passes validation. With the default install_opencode = true and opencode_version = "latest", the install path then treats that binary as already installed and skips the V2 installer, after which V2-only configuration and authentication commands fail. Validate that the detected major version is actually 2, rather than merely not 1.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Resolved in 0d75b8b by removing the installed-version check entirely, matching the other agent modules. With install_opencode = true the official V2 installer now always runs, so an existing 0.x or 1.x binary is replaced; with install_opencode = false the module only checks that opencode is on PATH.

Generated with Coder Agents.

Comment on lines +65 to +67
condition = var.auth_json == "" || try(alltrue([
for c in jsondecode(var.auth_json) : can(c.id) && can(c.integrationID) && can(c.label) && can(c.active) && can(c.value.type)
]) && length(jsondecode(var.auth_json)) > 0, false)

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 Badge Enforce an array in auth_json validation

A JSON object whose values contain the listed credential fields passes this expression because Terraform iterates over the object's values, even though the error message and runtime require a top-level array. For example, {"only":{"id":"x","integrationID":"anthropic","label":"x","active":true,"value":{"type":"key"}}} plans successfully but then fails every workspace startup at the explicit array check in setup_auth; validate the decoded top-level type here.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No longer applicable: all variable validation blocks were removed in 4a23861, including this auth_json check. The install script still requires a top-level JSON array and fails startup with a clear error otherwise.

Generated with Coder Agents.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Correction to my reply above: the install script has no array check of its own. The array requirement is enforced by opencode auth import, and startup still fails on a non-array auth_json. Verified with OpenCode v2.0.29 using the old v0 object format ({"anthropic":{"type":"api","key":"..."}}):

  • The logout loop finds no id, so no saved credential is logged out.
  • opencode auth import prints Expected array and exits 1, and the script stops with ERROR: OpenCode credential import failed.

No code change needed.

Generated with Coder Agents.

Comment on lines +99 to +100
condition = var.mcp == "" || try(alltrue([for name, server in jsondecode(var.mcp) : !contains(["servers", "timeout"], name) && !can(server.enabled)]), false)
error_message = "mcp must map server names to server objects (not the whole mcp block), and servers must use disabled instead of the V1 enabled field."

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 Badge Reject scalar MCP server entries

This check only rejects an enabled attribute, so values such as mcp = "{\"playwright\":\"invalid\"}" pass because attribute access on the scalar is not possible and !can(server.enabled) becomes true. The installer then writes that scalar under mcp.servers, although the OpenCode V2 configuration requires each named server to be an object, leaving OpenCode with an invalid MCP configuration; verify each decoded value is an object before accepting it.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No longer applicable: all variable validation blocks were removed in 4a23861, including this mcp check. Server objects are passed through to mcp.servers as given, per the OpenCode MCP docs format.

Generated with Coder Agents.

…ations

Drop the validation blocks on opencode_version, auth_json, config_json,
and mcp, and the plan tests that exercised them. opencode_version is now
base64-encoded into the install script like the other inputs, since it is
no longer format-checked before rendering.
…nd auth pre-check

Remove reload_running_service; at workspace start the shared service is
normally not running yet, and `opencode reload` applies config otherwise.
Pipe auth_json straight into `opencode auth import`, which validates its
own input. Credential rotation (logout of the module's ids) is kept.
Drop config_json so the module only writes MCP servers into opencode.json.
mcp keeps existing on-disk servers on duplicate names and now logs a
warning for each skipped name, matching `claude mcp add-json --scope user`
in the claude-code module (it errors on an existing name and keeps it).
@35C4n0r
35C4n0r marked this pull request as ready for review October 13, 2026 15:27
…ion checks

Always run the official installer when install_opencode is true, matching the other agent modules. Drops the OpenCode 1 rejection and the skip when the requested version is already installed.
@linear-code

linear-code Bot commented Oct 13, 2026

Copy link
Copy Markdown

REG-85

@coder

coder Bot commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Automated review requested by @matifali. Please sanity-check before acting on it.

Verdict: request changes (minor). No blockers. Two should-fix items: upgrade cleanup and auth input validation.

What's good

  • main.tf is clean and valid. Every variable is used, nothing references removed inputs, external/agentapi/task_app_id are gone, and scripts output plus coder-utils match codex and claude-code. Module data dir is .coder-modules/coder-labs/opencode, per AGENTS.md.
  • No leftovers of report_tasks, AgentAPI, coder_ai_task, CODER_MCP_APP_STATUS_SLUG, CODER_MCP_AI_AGENTAPI_URL, port 3284, coder exp mcp or ai_prompt in module code or README. The only matches are the test-util import path and a negative assertion (see nits).
  • The secret goes through coder_env (CODER_OPENCODE_AUTH_JSON), never into the script. Both tftest and TS tests check this.
  • Tests were rewritten, not just deleted: 7 tftest runs and about 15 container tests. They cover install via the official installer, latest→empty VERSION, installer retry, download failure, workdir, OPENCODE_CONFIG_DIR, MCP merge precedence, JSONC skip, auth import/rotate/no-leak, and pre/post scripts.
  • The README migration warning (L22) lists removed and renamed inputs, the auth_json format change and the OpenCode V1→V2 move.

Findings

  1. should-fix: the v0.x task-reporting MCP server survives upgrades. The old scripts/install.sh (L91-125 on main) wrote .mcp.coder into ~/.config/opencode/opencode.json. That entry runs coder exp mcp server with CODER_MCP_APP_STATUS_SLUG, CODER_MCP_AI_AGENTAPI_URL=http://localhost:3284, a plaintext CODER_AGENT_TOKEN and CODER_MCP_ALLOWED_TOOLS=coder_report_task. On a persistent home, v1 only rewrites the file when base_opencode_config is set. setup_mcp (scripts/install.sh.tftpl:176) keeps existing entries, and V2 still normalizes V1 top-level mcp.* entries. So OpenCode keeps spawning a dead task-reporting server with a stale agent token, which is exactly what this series removes.
    Fix: in the install script, delete .mcp.coder only when it matches the old module's signature, e.g. jq 'if .mcp.coder.environment.CODER_MCP_ALLOWED_TOOLS == "coder_report_task" then del(.mcp.coder) else . end' (skip JSONC). At minimum, tell users to remove it in the README warning (L22).
  2. should-fix: auth_json is not checked before the destructive logout, and a review reply describes code that isn't there. setup_auth (install.sh.tftpl:186-208) logs out matching ids (L196-200) before checking that the input is a valid array. The process substitution at L200 fails silently on bad input. Users upgrading with the old v0 auth.json object ({"anthropic":{...}}) only get a generic "credential import failed" error. Replying to the Codex P2 thread, the author said "The install script still requires a top-level JSON array and fails startup with a clear error otherwise". At this head there is no such check (no type == "array" anywhere in the script).
    Fix: add jq -en 'env.CODER_OPENCODE_AUTH_JSON | fromjson | type == "array" and all(.[]; has("id") and has("integrationID") and has("value"))' before the logout loop. On failure, exit with a message that names the old auth.json format and points to opencode auth export. This also reduces the Codex P1 risk (losing a working credential when the import fails). Add a TS test for an object-shaped auth_json.
  3. nit: stale v0 files are left behind. The old ~/.local/share/opencode/auth.json (plaintext keys, ignored by V2 on a fresh DB) and ~/.opencode-module/ are never cleaned up. Mention both in the migration note.
  4. nit: ln -s can abort the script on a dangling symlink (install.sh.tftpl:77-78). [ ! -e ] is true for a dangling symlink, so ln -s then fails with "File exists" under set -e. Use ln -sfn. The codex and claude-code scripts have the same pattern, so a series-wide fix makes sense.
  5. nit: tests import from ../../../coder/modules/agentapi/test-util (main.test.ts:18-21). The siblings do the same, but if the agentapi module is deleted later in this series, every migrated module's tests break. Consider moving extractCoderEnvVars/writeExecutable into ~test in a follow-up.
  6. nit: tftest gaps. There's no plan assertion that base_opencode_config is base64-encoded into the script (only mcp is), and nothing checks that ARG_WORKDIR is empty when workdir is null.
  7. nit: variable attribute order. It is type → description, and sensitive sits before default on auth_json (main.tf:53-58), while AGENTS.md asks for description → type → default → sensitive. The merged codex module uses the same order, so the series is at least internally consistent.

CI and reviews

  • Checks on a71d9bd: README validation ✅, typos/format ✅, lint ✅, "Validate Terraform output" ✅ (this job runs the TS tests, terraform tests, validate and shellcheck per ci.yaml). "Compare module scores against main" was still in progress.
  • The Codex bot left 4 comments on the old commit 83e4518. The author replied to all of them:
    • Legacy 0.x binary: obsolete, since the version check was removed.
    • auth_json and mcp validation (2 comments): obsolete, since the validation blocks were removed.
    • P1 logout-before-import: declined. The reasoning is fair, but see finding 2.
  • No human reviews yet. I can't see thread resolution state through the REST API.

Consistency with the series

  • It matches codex and claude-code: coder-utils, a scripts output, optional workdir, base_*_config overwrite then mcp merge with existing servers winning, no validation blocks, and a README warning plus examples-only README.
  • AI Gateway support (enable_ai_gateway in codex and claude-code) is explicitly out of scope. Fine, but worth a follow-up ticket.

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