Skip to content

fix(setup): bake OpenCode plugin binary fallback#115

Merged
Alan-TheGentleman merged 1 commit intomainfrom
fix/opencode-plugin-engram-bin
Mar 21, 2026
Merged

fix(setup): bake OpenCode plugin binary fallback#115
Alan-TheGentleman merged 1 commit intomainfrom
fix/opencode-plugin-engram-bin

Conversation

@Alan-TheGentleman
Copy link
Collaborator

Linked Issue

Closes #113

Summary

  • bake a safe absolute-path fallback into the installed OpenCode plugin copy so engram serve works in headless environments
  • make resolveEngramCommand() return the executable path on all platforms instead of assuming Unix PATH is always reliable
  • add focused tests for the patched ENGRAM_BIN resolution chain and setup behavior

Problem

After fixing MCP path resolution, the remaining OpenCode headless gap was the plugin runtime itself. The installed engram.ts still relied on process.env.ENGRAM_BIN ?? "engram", which breaks in systemd or other headless environments when PATH does not include the binary location.

Fix

The installed plugin copy now uses this precedence chain:

  1. process.env.ENGRAM_BIN
  2. Bun.which("engram")
  3. baked-in absolute path from os.Executable()

This preserves user override behavior while making headless setups work without manual service edits in the common case.

Test Plan

  • go test ./internal/setup ./cmd/engram ./internal/mcp ./internal/server ./internal/store ./internal/sync ./internal/tui ./internal/version
  • Added focused tests for plugin patching, absolute-path fallback behavior, and setup installation output

Copilot AI review requested due to automatic review settings March 21, 2026 23:13
@Alan-TheGentleman Alan-TheGentleman added the type:bug Bug fix label Mar 21, 2026
@Alan-TheGentleman Alan-TheGentleman merged commit 0fd1787 into main Mar 21, 2026
10 of 13 checks passed
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates Engram’s setup/install flow to make OpenCode’s plugin and MCP invocation resilient in headless environments by ensuring the plugin can reliably locate the engram binary without depending on inherited PATH.

Changes:

  • Patch the installed OpenCode plugin (engram.ts) to resolve ENGRAM_BIN via env override → Bun.which("engram") → baked-in absolute path.
  • Change resolveEngramCommand() to return os.Executable() on all platforms (falling back to bare engram only if os.Executable() fails).
  • Expand and adjust setup tests to cover the updated resolution chain and installation behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
internal/setup/setup.go Adds plugin patching for ENGRAM_BIN and updates command resolution to use os.Executable() cross-platform.
internal/setup/setup_test.go Updates and adds tests for absolute-path command resolution and OpenCode plugin patching behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +267 to +273
// Priority (left to right, first truthy wins):
// 1. ENGRAM_BIN env var — explicit user override, always respected.
// 2. Bun.which("engram") — runtime PATH lookup; works in interactive shells.
// 3. Absolute baked-in path — works in headless/systemd where PATH is stripped.
//
// If absBin is already bare "engram" (os.Executable fallback) we don't add it
// as the third fallback because it would be redundant with Bun.which("engram").
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

The comment says “first truthy wins”, but the patched TypeScript line uses the nullish coalescing operator (??), which selects the first non-nullish value (null/undefined), not the first truthy value. This matters because process.env.ENGRAM_BIN = "" would currently override the fallbacks. Please update the comment wording (and the bullet list) to reflect ?? semantics, or change the JS operator if you intend truthiness-based fallback.

Copilot uses AI. Check for mistakes.
Comment on lines +142 to +143
if cmd == "engram" {
t.Fatalf("expected absolute path from os.Executable(), got bare 'engram'")
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

The comment says this verifies an “absolute path”, but the assertions only check non-empty and != "engram". Consider either (a) adding an actual absolute-path assertion (e.g., filepath.IsAbs(cmd) on the current OS), or (b) adjusting the comment to match what’s being asserted.

Suggested change
if cmd == "engram" {
t.Fatalf("expected absolute path from os.Executable(), got bare 'engram'")
if !filepath.IsAbs(cmd) {
t.Fatalf("expected absolute path from os.Executable(), got %q", cmd)

Copilot uses AI. Check for mistakes.
Comment on lines +239 to +240
// will be the real absolute path in tests, not bare "engram".
if !strings.Contains(text, "command = ") || !strings.Contains(text, "engram") {
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

This test is currently non-deterministic / likely to fail: resolveEngramCommand() uses os.Executable(), so under go test the command string will typically be the test binary path (e.g. .../setup.test) which often does not contain "engram". To make this stable, stub osExecutable in this test (as you do elsewhere) and assert against that stubbed value (or at least assert on properties of the stubbed path).

Suggested change
// will be the real absolute path in tests, not bare "engram".
if !strings.Contains(text, "command = ") || !strings.Contains(text, "engram") {
// will be the real absolute path in tests, so we only assert that a command
// entry is present, not on the executable name itself.
if !strings.Contains(text, "command = ") {

Copilot uses AI. Check for mistakes.
Comment on lines +2354 to +2355
// truthy process.env.ENGRAM_BIN short-circuits before Bun.which and the
// baked-in path. This is the JavaScript ?? semantics guarantee.
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

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

The explanation here refers to a “truthy” env var short-circuiting, but the plugin code uses the nullish coalescing operator (??). With ??, an empty string is not treated as missing, so ENGRAM_BIN="" would still win and break spawning. Update the comment to say “non-nullish (not null/undefined)” (or consider using || if you actually want empty-string to fall through).

Suggested change
// truthy process.env.ENGRAM_BIN short-circuits before Bun.which and the
// baked-in path. This is the JavaScript ?? semantics guarantee.
// non-nullish (not null/undefined) process.env.ENGRAM_BIN short-circuits
// before Bun.which and the baked-in path, per JavaScript ?? semantics.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Handle ENGRAM_BIN automatically for OpenCode plugin serve mode

2 participants