fix(config): tolerate invalid OPENCODE_PERMISSION JSON (#28388)

This commit is contained in:
Kit Langton
2026-05-19 16:37:50 -04:00
committed by GitHub
parent b70b45964a
commit c035c35eba
3 changed files with 36 additions and 2 deletions

View File

@@ -21,7 +21,6 @@ export const Flag = {
OPENCODE_DISABLE_PRUNE: truthy("OPENCODE_DISABLE_PRUNE"),
OPENCODE_DISABLE_TERMINAL_TITLE: truthy("OPENCODE_DISABLE_TERMINAL_TITLE"),
OPENCODE_SHOW_TTFD: truthy("OPENCODE_SHOW_TTFD"),
OPENCODE_PERMISSION: process.env["OPENCODE_PERMISSION"],
OPENCODE_DISABLE_AUTOCOMPACT: truthy("OPENCODE_DISABLE_AUTOCOMPACT"),
OPENCODE_DISABLE_MODELS_FETCH: truthy("OPENCODE_DISABLE_MODELS_FETCH"),
OPENCODE_DISABLE_MOUSE: truthy("OPENCODE_DISABLE_MOUSE"),
@@ -59,6 +58,9 @@ export const Flag = {
get OPENCODE_PURE() {
return truthy("OPENCODE_PURE")
},
get OPENCODE_PERMISSION() {
return process.env["OPENCODE_PERMISSION"]
},
get OPENCODE_PLUGIN_META_FILE() {
return process.env["OPENCODE_PLUGIN_META_FILE"]
},

View File

@@ -703,7 +703,11 @@ export const layer = Layer.effect(
}
if (Flag.OPENCODE_PERMISSION) {
result.permission = mergeDeep(result.permission ?? {}, JSON.parse(Flag.OPENCODE_PERMISSION))
try {
result.permission = mergeDeep(result.permission ?? {}, JSON.parse(Flag.OPENCODE_PERMISSION))
} catch (err) {
log.warn("OPENCODE_PERMISSION contains invalid JSON, skipping", { err })
}
}
if (result.tools) {

View File

@@ -2079,6 +2079,34 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => {
})
})
// Regression for #28206: malformed OPENCODE_PERMISSION JSON used to crash
// the app on startup with an unhandled SyntaxError. Loading the config with
// an invalid JSON value in this env var should not throw.
describe("OPENCODE_PERMISSION env var", () => {
test("does not crash when OPENCODE_PERMISSION contains invalid JSON", async () => {
const original = process.env["OPENCODE_PERMISSION"]
process.env["OPENCODE_PERMISSION"] = "{invalid"
try {
await using tmp = await tmpdir()
await withTestInstance({
directory: tmp.path,
fn: async (ctx) => {
const config = await load(ctx)
// We don't assert on permission shape; the regression is that
// load() throws before returning anything.
expect(config).toBeDefined()
},
})
} finally {
if (original !== undefined) {
process.env["OPENCODE_PERMISSION"] = original
} else {
delete process.env["OPENCODE_PERMISSION"]
}
}
})
})
describe("OPENCODE_CONFIG_CONTENT token substitution", () => {
test("substitutes {env:} tokens in OPENCODE_CONFIG_CONTENT", async () => {
const originalEnv = process.env["OPENCODE_CONFIG_CONTENT"]