diff --git a/bridged/src/main/java/dev/ltms/bridged/worker/OpenCodeLauncher.java b/bridged/src/main/java/dev/ltms/bridged/worker/OpenCodeLauncher.java index da73778..c6a740b 100644 --- a/bridged/src/main/java/dev/ltms/bridged/worker/OpenCodeLauncher.java +++ b/bridged/src/main/java/dev/ltms/bridged/worker/OpenCodeLauncher.java @@ -144,7 +144,7 @@ public final class OpenCodeLauncher extends HerdrPeerLauncher { workerEnv.put("OPENCODE_CONFIG", writeConfig(cfg).toString()); } applyGitToken(workerEnv, cfg); - return new Launch(workerEnv, argvWithModel(cfg)); + return new Launch(workerEnv, argvWithModel(argvWithAuto(cfg), cfg)); } /** @@ -161,9 +161,24 @@ public final class OpenCodeLauncher extends HerdrPeerLauncher { return cfg.baseUrl() != null && !cfg.baseUrl().isBlank(); } - /** The launch argv plus, when a model is configured, the opencode {@code -m provider/model} flag. */ - private List argvWithModel(BridgedConfig.Worker cfg) { + /** + * The launch argv plus the unconditional {@code --auto} flag, which auto-approves the + * permissions opencode does not explicitly deny. It is unconditional, not a preference: a + * spawned peer has no human at its pane — the bridge spawned it — so one that stops at an + * approval prompt is a wedged agent, indistinguishable from a legitimate mid-turn wait and + * unable to end its turn with {@code bridge_reply}. opencode's own help calls this + * "dangerous!", but the blast radius here is already bounded by design: a worker runs in its + * own git worktree on its own branch, is off-subscription, and cannot merge — the lead is the + * gate. + */ + private List argvWithAuto(BridgedConfig.Worker cfg) { List argv = mutableArgv(cfg.argv()); + argv.add("--auto"); + return argv; + } + + /** The launch argv plus, when a model is configured, the opencode {@code -m provider/model} flag. */ + private List argvWithModel(List argv, BridgedConfig.Worker cfg) { if (cfg.model() != null && !cfg.model().isBlank()) { argv.add("-m"); argv.add(cfg.model()); diff --git a/bridged/src/test/java/dev/ltms/bridged/worker/OpenCodeLauncherTest.java b/bridged/src/test/java/dev/ltms/bridged/worker/OpenCodeLauncherTest.java index 57da6d2..d1a2fc2 100644 --- a/bridged/src/test/java/dev/ltms/bridged/worker/OpenCodeLauncherTest.java +++ b/bridged/src/test/java/dev/ltms/bridged/worker/OpenCodeLauncherTest.java @@ -99,21 +99,24 @@ class OpenCodeLauncherTest { } @Test - void passesTheModelAsDashMFlag(@TempDir Path root) { + void passesTheModelAsDashMFlagAlongsideAutoApprove(@TempDir Path root) { FakeHerdr herdr = new FakeHerdr(); service(herdr, root, opencodeCfg("google/gemini-2.5-pro", null, null)).spawn(); List args = startArgs(herdr); + assertTrue(args.contains("--auto"), + "--auto is present alongside -m so a spawned peer never blocks on approval"); int m = args.indexOf("-m"); assertTrue(m >= 0, "model is selected with -m"); assertEquals("google/gemini-2.5-pro", args.get(m + 1), "the provider/model selector follows -m"); } @Test - void noModelFlagWhenModelBlank(@TempDir Path root) { + void autoApproveIsUnconditionalWhenModelBlank(@TempDir Path root) { FakeHerdr herdr = new FakeHerdr(); service(herdr, root, opencodeCfg(null, null, null)).spawn(); - assertEquals(List.of(), startArgs(herdr), "no model → no extra args beyond the executable"); + assertEquals(List.of("--auto"), startArgs(herdr), + "--auto is unconditional: a model-less worker still must never block on approval"); } @Test