Skip to content

feat: source the opencode engine from a self-maintained fork#2166

Open
benjaminshafii wants to merge 1 commit into
devfrom
feat/self-hosted-opencode-engine
Open

feat: source the opencode engine from a self-maintained fork#2166
benjaminshafii wants to merge 1 commit into
devfrom
feat/self-hosted-opencode-engine

Conversation

@benjaminshafii

Copy link
Copy Markdown
Member

What

Switches the bundled opencode engine from anomalyco/opencode releases to our own fork different-ai/opencode, pinned at v1.16.2-openwork.0 — an identical rebuild of upstream v1.16.2 (zero patches), built by the fork's own engine-release workflow with upstream-identical asset names.

Why

We want to carry our own engine patches and control rollout timing without waiting on upstream, while still following upstream's release cadence.

How rollout works from now on

  • Single pin: constants.json now holds both opencodeVersion and opencodeRepo. Every consumer reads it: desktop sidecar (prepare-sidecar.mjs), orchestrator runtime downloads (was hardcoded), Daytona/microsandbox images, den-worker bundler, CI engine installs.
  • Cadence: the fork's upstream-sync workflow (daily cron) fast-forwards its dev mirror and auto-builds vX.Y.Z-openwork.0 whenever upstream publishes a release.
  • Validation gate: new engine-bump.yml here opens a PR bumping opencodeVersion when the fork has a newer release; CI on that PR installs the engine from the new release and runs the full suite. Nothing auto-merges. (Set ENGINE_BUMP_TOKEN PAT secret so bump PRs trigger CI.)
  • Patches: .patch files in the fork's openwork/patches/ are applied with git am on top of the upstream tag at build time; release notes record the upstream ref.

Signing

  • macOS: bun embeds an ad-hoc signature at compile time (verified: binary runs on Apple Silicon); prepare-sidecar re-codesigns and the release workflow signs + notarizes the app bundle. No change.
  • Linux/Docker: n/a.
  • Windows: fork CLI exes are unsigned (upstream used their Azure Trusted Signing). Known cosmetic regression inside the installed app; follow-up if we want our own cert.

Tested (commands + results)

  • Fork release build: engine-release run — success, all 12 assets published with upstream naming.
  • opencode --version1.16.2-openwork.0; opencode serve health → {"healthy":true,"version":"1.16.2-openwork.0"}.
  • node scripts/prepare-sidecar.mjs --force against this branch → downloaded from the fork, version check passed, codesigned, versions.json written (sha256 recorded).
  • Full E2E: OpenWork server (managed engine = fork binary) → add posthog MCP → engine reports {"posthog":{"status":"needs_auth"}}.
  • pnpm typecheck in apps/orchestrator — clean; node --check on all edited .mjs scripts; bash -n on edited shell scripts.
  • This PR's own CI is the final gate: ci-tests.yml now installs the engine from different-ai/opencode v1.16.2-openwork.0 and runs the suite against it.

Known follow-ups

  • No download-time sha256 verification on engine fetch paths (pre-existing; worth adding now that we own the supply chain).
  • opencode-chrome-devtools plugin still fetched unpinned from npm at runtime (pre-existing).
  • README install instructions still reference opencode.ai/install (docs only).

Centralize the engine origin in constants.json (opencodeRepo) next to the
existing opencodeVersion pin, and point both at different-ai/opencode
v1.16.2-openwork.0 (identical rebuild of anomalyco/opencode v1.16.2).

- prepare-sidecar.mjs: default repo from constants.json (env overrides kept)
- orchestrator: inject __OPENWORK_PINNED_OPENCODE_REPO__ at build time and
  use it for runtime engine downloads (was hardcoded)
- den-worker install-opencode.mjs + both Dockerfiles: repo from
  constants.json via OPENCODE_GITHUB_REPO build arg
- ci-tests/nightly-evals: fallback repo from constants.json
- opencode-agents: install from pinned release assets instead of
  opencode.ai/install (which cannot resolve fork versions)
- runtime.mjs: fork-aware engine install hint
- new engine-bump.yml: opens a CI-gated bump PR when the fork publishes a
  new vX.Y.Z-openwork.N release (fork tracks upstream cadence via its own
  upstream-sync workflow)
@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openwork-app Ready Ready Preview, Comment Jun 11, 2026 2:05am
openwork-den Ready Ready Preview, Comment Jun 11, 2026 2:05am
openwork-den-worker-proxy Ready Ready Preview, Comment Jun 11, 2026 2:05am
openwork-landing Ready Ready Preview, Comment, Open in v0 Jun 11, 2026 2:05am

@cubic-dev-ai cubic-dev-ai 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.

5 issues found across 14 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/electron/runtime.mjs">

<violation number="1" location="apps/desktop/electron/runtime.mjs:941">
P2: Engine download flow skips checksum/signature verification before extracting a binary to the executable path.</violation>

<violation number="2" location="apps/desktop/electron/runtime.mjs:943">
P2: Unsupported-architecture fallback returns a non-command string that `bash -lc` executes and fails immediately.</violation>
</file>

<file name="ee/apps/den-worker-runtime/scripts/install-opencode.mjs">

<violation number="1" location="ee/apps/den-worker-runtime/scripts/install-opencode.mjs:157">
P2: Bundle cache invalidation ignores `opencodeRepo`, so repo changes with the same version can leave a stale binary from the wrong source.</violation>
</file>

<file name=".github/workflows/engine-bump.yml">

<violation number="1" location=".github/workflows/engine-bump.yml:59">
P1: grep check always matches when no PR exists, preventing bump PRs from ever being created.

When `gh pr list` returns no PRs, it outputs `[]`. The jq filter `.[0].number` on an empty array yields `null`. The pipe passes the literal string "null" to `grep -q .`, which matches ("null" contains characters), so the condition is truthy and the script exits early with "Bump PR for $latest already open" — even when no PR exists.</violation>
</file>

<file name="apps/orchestrator/src/cli.ts">

<violation number="1" location="apps/orchestrator/src/cli.ts:144">
P2: Fallback default in pinnedOpencodeRepo() points to upstream repo ("anomalyco/opencode") instead of the fork ("different-ai/opencode") from constants.json. If both env vars and the compile-time define are absent, the download URL silently targets the wrong repo, which lacks the fork's release tags.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

set -euo pipefail
latest="${{ steps.check.outputs.latest }}"
branch="engine-bump/${latest}"
if gh pr list --head "$branch" --state open --json number -q '.[0].number' | grep -q .; then

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: grep check always matches when no PR exists, preventing bump PRs from ever being created.

When gh pr list returns no PRs, it outputs []. The jq filter .[0].number on an empty array yields null. The pipe passes the literal string "null" to grep -q ., which matches ("null" contains characters), so the condition is truthy and the script exits early with "Bump PR for $latest already open" — even when no PR exists.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/engine-bump.yml, line 59:

<comment>grep check always matches when no PR exists, preventing bump PRs from ever being created.

When `gh pr list` returns no PRs, it outputs `[]`. The jq filter `.[0].number` on an empty array yields `null`. The pipe passes the literal string "null" to `grep -q .`, which matches ("null" contains characters), so the condition is truthy and the script exits early with "Bump PR for $latest already open" — even when no PR exists.</comment>

<file context>
@@ -0,0 +1,78 @@
+          set -euo pipefail
+          latest="${{ steps.check.outputs.latest }}"
+          branch="engine-bump/${latest}"
+          if gh pr list --head "$branch" --state open --json number -q '.[0].number' | grep -q .; then
+            echo "Bump PR for $latest already open"
+            exit 0
</file context>
Suggested change
if gh pr list --head "$branch" --state open --json number -q '.[0].number' | grep -q .; then
if gh pr list --head "$branch" --state open --json number -q '.[0].number' | grep -q '^[0-9]\+$'; then

: `unzip -o /tmp/${asset} -d "$HOME/.opencode/bin"`;
return `mkdir -p "$HOME/.opencode/bin" && curl -fsSL ${url} -o /tmp/${asset} && ${extract}`;
}
return `Download opencode v${version} from https://github.com/${repo}/releases/tag/v${version}`;

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: Unsupported-architecture fallback returns a non-command string that bash -lc executes and fails immediately.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/electron/runtime.mjs, line 943:

<comment>Unsupported-architecture fallback returns a non-command string that `bash -lc` executes and fails immediately.</comment>

<file context>
@@ -920,6 +920,28 @@ export function createRuntimeManager({ app, desktopRoot, listLocalWorkspacePaths
+          : `unzip -o /tmp/${asset} -d "$HOME/.opencode/bin"`;
+        return `mkdir -p "$HOME/.opencode/bin" && curl -fsSL ${url} -o /tmp/${asset} && ${extract}`;
+      }
+      return `Download opencode v${version} from https://github.com/${repo}/releases/tag/v${version}`;
+    }
     return `curl -fsSL https://opencode.ai/install | bash -s -- --version ${version} --no-modify-path`;
</file context>

const extract = asset.endsWith(".tar.gz")
? `tar -xzf /tmp/${asset} -C "$HOME/.opencode/bin"`
: `unzip -o /tmp/${asset} -d "$HOME/.opencode/bin"`;
return `mkdir -p "$HOME/.opencode/bin" && curl -fsSL ${url} -o /tmp/${asset} && ${extract}`;

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: Engine download flow skips checksum/signature verification before extracting a binary to the executable path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/electron/runtime.mjs, line 941:

<comment>Engine download flow skips checksum/signature verification before extracting a binary to the executable path.</comment>

<file context>
@@ -920,6 +920,28 @@ export function createRuntimeManager({ app, desktopRoot, listLocalWorkspacePaths
+        const extract = asset.endsWith(".tar.gz")
+          ? `tar -xzf /tmp/${asset} -C "$HOME/.opencode/bin"`
+          : `unzip -o /tmp/${asset} -d "$HOME/.opencode/bin"`;
+        return `mkdir -p "$HOME/.opencode/bin" && curl -fsSL ${url} -o /tmp/${asset} && ${extract}`;
+      }
+      return `Download opencode v${version} from https://github.com/${repo}/releases/tag/v${version}`;
</file context>

Comment on lines +157 to 158
const { version, repo: opencodeRepo } = resolveOpencodeVersion();
const versionLabel = version;

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: Bundle cache invalidation ignores opencodeRepo, so repo changes with the same version can leave a stale binary from the wrong source.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ee/apps/den-worker-runtime/scripts/install-opencode.mjs, line 157:

<comment>Bundle cache invalidation ignores `opencodeRepo`, so repo changes with the same version can leave a stale binary from the wrong source.</comment>

<file context>
@@ -153,7 +154,7 @@ function findBinary(searchRoot) {
 }
 
-const version = resolveOpencodeVersion();
+const { version, repo: opencodeRepo } = resolveOpencodeVersion();
 const versionLabel = version;
 
</file context>
Suggested change
const { version, repo: opencodeRepo } = resolveOpencodeVersion();
const versionLabel = version;
const { version, repo: opencodeRepo } = resolveOpencodeVersion();
const versionLabel = `${opencodeRepo}@${version}`;

) {
return __OPENWORK_PINNED_OPENCODE_REPO__.trim();
}
return "anomalyco/opencode";

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: Fallback default in pinnedOpencodeRepo() points to upstream repo ("anomalyco/opencode") instead of the fork ("different-ai/opencode") from constants.json. If both env vars and the compile-time define are absent, the download URL silently targets the wrong repo, which lacks the fork's release tags.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/orchestrator/src/cli.ts, line 144:

<comment>Fallback default in pinnedOpencodeRepo() points to upstream repo ("anomalyco/opencode") instead of the fork ("different-ai/opencode") from constants.json. If both env vars and the compile-time define are absent, the download URL silently targets the wrong repo, which lacks the fork's release tags.</comment>

<file context>
@@ -128,6 +128,21 @@ const FALLBACK_VERSION = "0.1.0";
+  ) {
+    return __OPENWORK_PINNED_OPENCODE_REPO__.trim();
+  }
+  return "anomalyco/opencode";
+}
 const DEFAULT_OPENWORK_PORT = 8787;
</file context>

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.

1 participant