From e8c05c24a6a97de4cfe4048a42108a6c1b92a599 Mon Sep 17 00:00:00 2001 From: Will Washburn Date: Wed, 29 Apr 2026 15:09:34 -0700 Subject: [PATCH] Fix opencode harness: use object form for wildcard permission MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit opencode 1.14.x rejects `permission: "allow"` in agent config. Its agent-config decoder runs `Object.assign({}, $.permission)` before the schema's string-normalizer fires, so a bare string gets spread into its indexed chars and each char fails validation against "ask" | "allow" | "deny" ("Expected PermissionActionConfig, got 'a' / 'l' / 'l' / 'o' / 'w'"). Emit `{ "*": "allow" }` instead — the schema-canonical wildcard form that opencode's own normalizer produces internally. This restores the build-agent-equivalent permissions that the bare string previously granted, and is forward-compatible with older opencode versions that accepted the string form. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/harness-kit/src/harness.test.ts | 7 ++++-- packages/harness-kit/src/harness.ts | 27 ++++++++++++++++-------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/packages/harness-kit/src/harness.test.ts b/packages/harness-kit/src/harness.test.ts index a30b4bef..0d684b0d 100644 --- a/packages/harness-kit/src/harness.test.ts +++ b/packages/harness-kit/src/harness.test.ts @@ -153,8 +153,11 @@ test('opencode configFiles carries a well-formed opencode.json with the agent de // Wildcard-allow across opencode's tool set — matches the built-in // `build` agent. Without this, opencode's restrictive default kept // agents from making any edits and autosync had nothing to - // propagate on exit. - permission: 'allow' + // propagate on exit. Object form (not bare 'allow' string) because + // opencode 1.14.x's agent-config decoder Object.assigns the value + // before its string-normalizer runs, which mangles strings into + // their indexed chars. + permission: { '*': 'allow' } } } }); diff --git a/packages/harness-kit/src/harness.ts b/packages/harness-kit/src/harness.ts index f0495480..514094de 100644 --- a/packages/harness-kit/src/harness.ts +++ b/packages/harness-kit/src/harness.ts @@ -194,25 +194,34 @@ export function buildInteractiveSpec(input: BuildInteractiveSpecInput): Interact // with the persona's prompt + full-provider-form model, selected via // `--agent ` at launch. We emit that file via configFiles // so the CLI can drop it into the mount dir before exec. - // `permission: 'allow'` is wildcard-allow across every opencode tool - // (read / edit / bash / webfetch / etc.), matching the built-in - // `build` agent's effective permissions. Without this, opencode - // applies its restrictive default and agent-side edits never reach - // the mount (the user-visible symptom: "I asked the agent to change - // files and nothing synced"). The mount already sandboxes writes - // so wildcard-allow does not escape to the real repo outside of - // autosync, and callers who want a read-only persona (e.g. a code + // `permission: { '*': 'allow' }` is wildcard-allow across every + // opencode tool (read / edit / bash / webfetch / etc.), matching the + // built-in `build` agent's effective permissions. Without this, + // opencode applies its restrictive default and agent-side edits never + // reach the mount (the user-visible symptom: "I asked the agent to + // change files and nothing synced"). The mount already sandboxes + // writes so wildcard-allow does not escape to the real repo outside + // of autosync, and callers who want a read-only persona (e.g. a code // reviewer) can override this in a follow-up PR that threads a // richer permission spec through the persona config — the current // harness-kit PersonaPermissions shape is claude-specific and // already warned about for opencode. + // + // The bare-string form `permission: 'allow'` was valid in older + // opencode versions but is rejected by 1.14.x: the agent decoder + // runs `Object.assign({}, $.permission)` before the schema's own + // string→`{'*': str}` normalizer fires, which spreads the string's + // indexed chars and then fails validation against + // `'ask' | 'allow' | 'deny'` ("Expected PermissionActionConfig, got + // 'a' / 'l' / 'l' / 'o' / 'w'"). Emitting the object form directly + // avoids that pre-decode step. const agentConfig = { agent: { [personaId]: { model, prompt: systemPrompt, mode: 'primary', - permission: 'allow' + permission: { '*': 'allow' } } } };