feat: add OpenCode devcontainer feature#63
Conversation
There was a problem hiding this comment.
Pull Request Overview
Adds a new DevContainer Feature for installing and using OpenCode (AI coding agent) with support for latest/pinned versions, multi-arch handling, tests, and CI integration.
- Implements feature definition, installation script with version resolution and retries, and adds tests (default and pinned version).
- Updates README, scenarios, global test aggregation, and workflow matrix.
- Also modifies unrelated files (.devcontainer.json and removes a commit message prompt), which appears outside the stated scope.
Reviewed Changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/opencode/devcontainer-feature.json | Defines new feature metadata and options. |
| src/opencode/install.sh | Implements version resolution, download, extraction, and installation logic. |
| test/opencode/test.sh | Adds basic installation verification test. |
| test/_global/opencode-specific-version.sh | Adds pinned version test. |
| test/_global/scenarios.json | Adds scenarios for default and specific version installs. |
| test/_global/all-tools.sh | Adds OpenCode to aggregate tool checks. |
| README.md | Documents new feature and usage. |
| .github/workflows/test.yaml | Adds feature to CI matrix. |
| .github/prompts/new-feature.prompt.md | Adds prompt for generating new features (internal tooling). |
| .github/copilot-instructions.md | Adds an extra testing instruction line. |
| .devcontainer.json | Replaces an existing feature with a different one (appears unrelated). |
| .github/prompts/generate-commit-message.prompt.md | File contents removed (appears unrelated to feature addition). |
| # Checks if packages are installed and installs them if not | ||
| check_packages() { | ||
| if ! dpkg -s "$@" >/dev/null 2>&1; then | ||
| if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then |
There was a problem hiding this comment.
The use of find /var/lib/apt/lists/* when the directory is empty can cause find to exit non‑zero (pattern not expanding), and with set -e the script may terminate before running apt-get update. Replace with a safer check such as: if [ "$(ls -1 /var/lib/apt/lists 2>/dev/null | wc -l)" = "0" ]; then or test -z "$(ls -A /var/lib/apt/lists 2>/dev/null)".
| if [ "$(find /var/lib/apt/lists/* | wc -l)" = "0" ]; then | |
| if [ -z "$(ls -A /var/lib/apt/lists 2>/dev/null)" ]; then |
| resolve_latest_version_fallback() { | ||
| echo "Attempting to resolve latest version from releases page HTML..." >&2 | ||
| local releases_page | ||
| releases_page=$(curl -s --max-time 10 "https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/latest" 2>/dev/null || echo "") |
There was a problem hiding this comment.
The function echoes a fallback version but returns exit code 1; when assigned (RESOLVED_VERSION=$(resolve_latest_version_fallback)) under set -e this non-zero exit status will abort the script. Return 0 after emitting the fallback version (or wrap the assignment with || true) so the fallback path works as intended.
|
|
||
| echo "Failed to resolve version from HTML, using known fallback version..." >&2 | ||
| echo "0.10.4" # Known recent version as last resort | ||
| return 1 |
There was a problem hiding this comment.
The function echoes a fallback version but returns exit code 1; when assigned (RESOLVED_VERSION=$(resolve_latest_version_fallback)) under set -e this non-zero exit status will abort the script. Return 0 after emitting the fallback version (or wrap the assignment with || true) so the fallback path works as intended.
| return 1 | |
| return 0 |
|
|
||
| # Download with retries and proper error handling | ||
| for attempt in 1 2 3; do | ||
| if curl -fL --retry 3 --retry-delay 2 -o "${ASSET_NAME}" "${DOWNLOAD_URL}"; then |
There was a problem hiding this comment.
You are performing an outer manual retry loop while also using curl's built-in --retry 3, potentially causing up to 9 attempts and unnecessary delay; remove either the outer loop or the curl --retry flags to avoid redundant retries.
| if curl -fL --retry 3 --retry-delay 2 -o "${ASSET_NAME}" "${DOWNLOAD_URL}"; then | |
| if curl -fL -o "${ASSET_NAME}" "${DOWNLOAD_URL}"; then |
| fi | ||
| } | ||
|
|
||
| # Make sure we have curl, ca-certificates, and unzip |
There was a problem hiding this comment.
The comment omits jq which is required later (for parsing JSON); update the comment to reflect all required packages (e.g., "curl, ca-certificates, unzip, jq").
| # Make sure we have curl, ca-certificates, and unzip | |
| # Make sure we have curl, ca-certificates, unzip, and jq |
| "options": { | ||
| "version": { |
There was a problem hiding this comment.
The feature metadata is missing an instantiationMode field (e.g., "instantiationMode": "onCreate") as outlined in the repository’s feature authoring conventions; adding it makes execution timing explicit.
| "options": { | |
| "version": { | |
| "instantiationMode": "onCreate", | |
| "options": { |
| "ghcr.io/jsburckhardt/devcontainer-features/gic:1": {} | ||
|
|
||
|
|
||
| "ghcr.io/devcontainers/features/github-cli:latest": {} |
There was a problem hiding this comment.
This change replaces (and removes) the previously declared gic feature but appears unrelated to adding the opencode feature; revert or move to a separate PR to keep scope focused.
| "ghcr.io/devcontainers/features/github-cli:latest": {} | |
| "ghcr.io/devcontainers/features/github-cli:latest": {}, | |
| "ghcr.io/someorg/gic:latest": {} |
| # Resolve version | ||
| if [ "$OPENCODE_VERSION" = "latest" ]; then | ||
| if ! RESOLVED_VERSION=$(resolve_latest_version); then | ||
| RESOLVED_VERSION=$(resolve_latest_version_fallback) |
There was a problem hiding this comment.
Due to resolve_latest_version_fallback currently returning exit code 1 on its success path, line 106 will trigger set -e termination; once the fallback function is fixed to return 0, this block will work—alternatively guard the assignment with || true to prevent premature exit.
| RESOLVED_VERSION=$(resolve_latest_version_fallback) | |
| RESOLVED_VERSION=$(resolve_latest_version_fallback) || true |
Summary
This PR adds the OpenCode DevContainer Feature to the repository.
OpenCode is an AI coding agent built for the terminal - an open-source alternative to Claude Code with support for multiple LLM providers (Anthropic, OpenAI, Google, and local models).
Changes Made
New Feature Implementation
opencodesst/opencode/usr/local/bin/opencodeFiles Added/Modified
src/opencode/devcontainer-feature.json- Feature configurationsrc/opencode/install.sh- Installation script with robust error handlingtest/opencode/test.sh- Basic installation testtest/_global/opencode-specific-version.sh- Version-specific testtest/_global/all-tools.shandtest/_global/scenarios.json.github/workflows/test.yamlCI matrixREADME.mdwith feature documentationKey Features
Architecture Support
Version Handling
"version": "0.10.0")Installation Features
Usage Examples
Basic Installation
{ "image": "mcr.microsoft.com/devcontainers/base:ubuntu", "features": { "ghcr.io/jsburckhardt/devcontainer-features/opencode:1": {} } }With Specific Version
{ "image": "mcr.microsoft.com/devcontainers/base:ubuntu", "features": { "ghcr.io/jsburckhardt/devcontainer-features/opencode:1": { "version": "0.10.0" } } }Testing
Implementation Details
Version Resolution Strategy
/repos/sst/opencode/releases/latestRelease Asset Pattern
opencode-linux-{arch}.zipopencode-linux-x64.zip,opencode-linux-arm64.zipCompliance
Notes
This implementation prioritizes reliability and user experience:
The feature has been tested and verified to work correctly on Ubuntu with both latest version resolution and specific version pinning.