Skip to content

[Fix] OpenCode SDK server fails to start on Railway: root-owned HOME artifacts in the inference image - #129

Merged
mrubens merged 2 commits into
developfrom
fix/opencode-runtime-home-permissions
Jul 10, 2026
Merged

[Fix] OpenCode SDK server fails to start on Railway: root-owned HOME artifacts in the inference image#129
mrubens merged 2 commits into
developfrom
fix/opencode-runtime-home-permissions

Conversation

@mrubens

@mrubens mrubens commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Problem

On nightly (Railway), the LLM router falls back on every request, and task title refresh / work-kind classification fail:

[LLM Router] Routing fallback ... reason="OpenCode SDK server exited before it was ready ... code 1"
PermissionDenied: FileSystem.open (/tmp/.local/share/opencode/log/opencode.log)

Root cause: runtime-inference-base (#114) installs OpenCode and runs opencode --version as root with the image's HOME=/tmp. That bakes root-owned /tmp/.npm and /tmp/.local/share/opencode/ into the image layer. The final stage runs as the hardened non-root roomote-app user (#105) with the same HOME, which cannot write inside those root-owned directories, so the in-process OpenCode SDK server dies at startup.

Why nothing caught it:

Fix

  • .docker/app/Dockerfile: remove /tmp/.npm, /tmp/.local, and /tmp/.cache in the same RUN layer that installs and version-checks OpenCode, so the image ships a pristine /tmp and the runtime user creates its own HOME state.
  • deploy/ci/deployment-smoke.sh: the candidate check now executes opencode --version as the container user instead of just locating it. This verifies runtime executability (startup writes state under HOME); the comment documents that the tmpfs masks the raw-image case, which the Dockerfile layer now prevents.

Testing

  • bash -n on the smoke script. Local Docker daemon is unhealthy on this machine, so the image build is validated by this PR's Docker Build checks and, post-merge, by the (re-armed) deployment acceptance gate on the develop publish run.
  • Verification on nightly after deploy: api logs should show no PermissionDenied ... opencode.log, and [LLM Router] lines should stop reporting phase="fallback" with the SDK-server-exit reason.

The runtime-inference-base stage installs and version-checks OpenCode as
root with the image's HOME=/tmp, baking root-owned /tmp/.npm and /tmp/.local
(OpenCode's data dir) into the layer. The hardened runtime user shares that
HOME, so on hosts that run the raw image without a tmpfs over /tmp (Railway)
OpenCode dies at startup with PermissionDenied opening its log, taking the
LLM router, title refresh, and work-kind classification down with it.
Compose deployments mask the problem behind a fresh tmpfs, which is why the
acceptance gate never saw it.

Remove the artifacts in the same layer, and upgrade the smoke test's
candidate check from locating opencode to executing it as the container
user.
@roomote-roomote

roomote-roomote Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

No new code issues found. The previously flagged issue is resolved. See task

  • .docker/app/Dockerfile — the OpenCode install/version-check layer no longer leaves root-owned dotdirs in the shipped /tmp. Resolved by running the whole step under a disposable HOME=/tmp/opencode-build and rm -rf-ing it in the same RUN, so .npm/.local/.config/.cache (and any future dotdir) never persist in the layer.

Review summary

The fix correctly targets the root cause: the runtime user shares HOME=/tmp and could not write into the root-owned OpenCode data dir baked in by the build. Stripping /tmp/.local (which holds .local/share/opencode/log/opencode.log, the exact path in the crash) resolves the reported PermissionDenied. The smoke-test upgrade to actually execute opencode --version and the honest tmpfs caveat in the comment are both good.

Sync update (a731217): The latest commit replaces the fixed rm -rf /tmp/.npm /tmp/.local /tmp/.cache allowlist with a disposable HOME=/tmp/opencode-build scoped to the single RUN, removed via rm -rf /tmp/opencode-build at the end of the same chain. This is the more robust of the two options suggested in the initial review: every dotdir npm or OpenCode writes now lands under the disposable HOME and is removed, so the previously-omitted /tmp/.config leftover no longer ships and there is no allowlist to maintain. The export is scoped to that build shell, so the runtime ENV HOME=/tmp from runtime-base is unaffected. No new issues found.

# Severity File Line Issue
1 ✅ Resolved .docker/app/Dockerfile 285 /tmp/.config (and any other root-owned dotdir) leftover is eliminated by the disposable-HOME approach.

Review found the cleanup allowlist missed /tmp/.config, which the root-run
version check also creates. Instead of maintaining an allowlist, run the
whole install/check under HOME=/tmp/opencode-build and remove it, so any
dotdir npm or OpenCode writes now or in future versions stays out of the
shipped layer.
@mrubens

mrubens commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review finding: rather than adding /tmp/.config to the allowlist, the install/version-check layer now runs under a disposable HOME=/tmp/opencode-build that is removed at the end of the same RUN — the reviewer's second, more robust option. Any dotdir npm or OpenCode writes (now or in future versions) stays out of the shipped layer, with no allowlist to maintain. Runtime HOME=/tmp from runtime-base is unaffected since the export is scoped to that one RUN shell.

@mrubens
mrubens merged commit 0808e22 into develop Jul 10, 2026
@mrubens
mrubens deleted the fix/opencode-runtime-home-permissions branch July 12, 2026 04:19
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