From 2f1bb2f8b59fae3c0c105d5cff11c6b8bf5fbee9 Mon Sep 17 00:00:00 2001 From: LifeJiggy Date: Tue, 26 May 2026 01:39:54 +0100 Subject: [PATCH] fix(config): handle os.userInfo() failure in container/sandbox environments The loadInstanceState function calls os.userInfo().username to set a default username when none is configured. In minimal Docker/OCI containers or sandboxed environments where the passwd entry is unavailable, os.userInfo() throws an error. Wrap in try/catch with user fallback to prevent config initialization failure. --- packages/opencode/src/config/config.ts | 8 ++++++- packages/opencode/test/config/config.test.ts | 24 +++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 307b02ca4d9f..d3c449138039 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -762,7 +762,13 @@ export const layer = Layer.effect( result.permission = mergeDeep(perms, result.permission ?? {}) } - if (!result.username) result.username = os.userInfo().username + if (!result.username) { + try { + result.username = os.userInfo().username + } catch { + result.username = "user" + } + } if (result.autoshare === true && !result.share) { result.share = "auto" diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 6ce0acdb2a7b..462feba81bf5 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -1,4 +1,4 @@ -import { test, expect, describe, afterEach, beforeEach } from "bun:test" +import { test, expect, describe, afterEach, beforeEach, beforeAll, afterAll, spyOn } from "bun:test" import { Effect, Exit, Layer, Option } from "effect" import { FetchHttpClient, HttpClient, HttpClientResponse } from "effect/unstable/http" import { NodeFileSystem, NodePath } from "@effect/platform-node" @@ -26,6 +26,7 @@ import { import { InstanceRuntime } from "@/project/instance-runtime" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { testEffect } from "../lib/effect" +import os from "os" import path from "path" import fs from "fs/promises" import { pathToFileURL } from "url" @@ -1971,3 +1972,24 @@ test("parseManagedPlist handles empty config", async () => { ) expect(config.$schema).toBe("https://opencode.ai/config.json") }) + +describe("os.userInfo fallback", () => { + let userInfoSpy: ReturnType + + beforeAll(() => { + userInfoSpy = spyOn(os, "userInfo").mockImplementation(() => { + throw new Error("ENOENT") + }) + }) + + afterAll(() => { + userInfoSpy.mockRestore() + }) + + it.instance("falls back to 'user' when os.userInfo() fails", () => + Effect.gen(function* () { + const config = yield* Config.use.get() + expect(config.username).toBe("user") + }), + ) +})