Repository navigation
Conversation
…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.
Module Scorecard Check
|
| 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.
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 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 |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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.
| is_opencode_v1() { | ||
| [[ "$1" =~ ^1\. ]] | ||
| } |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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.
| 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) |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 importprintsExpected arrayand exits 1, and the script stops withERROR: OpenCode credential import failed.
No code change needed.
Generated with Coder Agents.
| 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." |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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).
…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.
Restore config_json as base_opencode_config. When set it overwrites the global opencode.json on every start, then mcp is applied.
|
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
Findings
CI and reviews
Consistency with the series
|
Closes REG-85
Refactor
coder-labs/opencodeto install and configure OpenCode 2 only, matching the codex, copilot, and cursor-cli (#1152) migrations. Targets the OpenCode V2 docs.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-reportingcoderMCP server, and thetask_app_idoutput.https://opencode.ai/v2/install, temp file +bash -nvalidation, retrying curl,--no-modify-path). The installer run itself is retried up to 3 times because its latest-version lookup intermittently returns 500.latestis passed as an emptyVERSION. Like the other agent modules, the installer runs on every start wheninstall_opencode = true; no installed-version check.opencode_versionis base64-encoded into the script.workdiroptional (created if missing). OpenCode has no trust prompt.config_jsontobase_opencode_config. When set it overwrites the globalopencode.json($OPENCODE_CONFIG_DIR, else$XDG_CONFIG_HOME/opencode, else~/.config/opencode) on every start.mcpvariable (server map) added under V2'smcp.serversafter the base config. Servers already in the file win on duplicate names, likeclaude 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,mcpis skipped with a warning.auth_jsontakes the JSON array printed byopencode auth exportand is applied withopencode auth import --standalone. Delivered throughcoder_envCODER_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.continueandsession_id: they only fed the launch command, so they become--continue/--sessionflags in the user'scoder_app.scriptsoutput; README reduced to examples and doc links; rewrite tests. Bump to1.0.0.Important
Worth a reviewer check:
opencode mcp addis 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_jsonis exported workspace-wide asCODER_OPENCODE_AUTH_JSON, visible to every workspace process (same exposure as provider API key env vars).auth_jsonare passed through toauth importunchanged 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:VERSION/--version;latestmust be empty; latest lookup (opencode.ai/update/api/latest/cli/npm) intermittently 500slatestto empty; retry installer run 3x; base64-encode the version~/.opencode/bin(plus anopencode2shim)--no-modify-path; module links intoCODER_SCRIPT_BIN_DIRand updates profilesmodelconfig key or agent/command model refsmodelvariable; users pick the model in OpenCodeauth.jsonis only imported by a migration that a fresh DB skips (schema bootstrap)auth_json=auth exportarray, applied viaauth import; provider env vars documented as alternativemanaged_settingsroot-owned policy/etc/opencode/opencode.json(absent fromdebug configsources and the binary). Policies come from config (experimental.policies) or OpenCode Consolemanaged_settings; no config input remains, so policies are left to the user or OpenCode Consolemcp.servers, usesdisabled; V1 top-level entries still normalized;enabledinsideserversis droppedmcp.servers; existing wins, matchingclaude mcp add-jsonOPENCODE_CONFIG_DIR>XDG_CONFIG_HOME/opencode>~/.config/opencode(opencode debug paths config)opencode reloadcontinue,session_id)--continue,--session,--prompt,--autoCLI flagscoder_appTesting
terraform fmt,terraform validate,terraform test(7 passed)bun test main.test.ts(15 passed, container-based)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 undermcp.servers(confirmed viaopencode debug config); credential imported, rotated on rerun, a separate user account preserved, secret absent from the logcoder exp syncscript, MCP merge (user server kept),everythingMCP server connected and its tool used inopencode run, model reply through Coder AI Gateway, credential import and rotation, restart, pinned 2.0.27 downgradereadmevalidation; all v2 docs links return 200Generated with Coder Agents.