fix(grok-local): stop defaulting --permission-mode to dontAsk (#11898)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The `grok_local` adapter runs the native Grok Build CLI in headless
mode for unattended agent heartbeats
> - Grok CLI 1.0 started to enforce the `dontAsk` permission mode as
deny-by-default, and it takes precedence over `--always-approve`
> - The adapter passes both flags on every run, so each run dies on its
first tool call and is still recorded as a success
> - This pull request removes the `dontAsk` default so unattended runs
rely on `--always-approve` alone
> - The benefit is that `grok_local` agents can execute tools again on
current Grok CLI releases
## Linked Issues or Issue Description
No public issue exists. Description per the bug template:
**What happened?**
Every `grok_local` run on Grok CLI 1.0.x stops on its first tool call.
The stream shows the tool call move from `pending` to `failed` with
"User cancelled the execution for tool `run_terminal_command`", and the
session ends with `stopReason: "cancelled"` after one turn. The CLI
exits 0, so Paperclip records the run as succeeded with no work done,
and the issue lands in missing-disposition recovery.
**Expected behavior**
Unattended runs must auto-approve tool executions. The adapter already
passes `--always-approve` for this.
**Steps to reproduce**
In a clean Linux environment with Grok CLI 1.0.3 and `XAI_API_KEY` set,
run the adapter's exact invocation shape:
`grok --output-format streaming-json --permission-mode dontAsk
--always-approve --disable-web-search --single "Run the shell command:
echo ok"`
The tool call is denied. Drop `--permission-mode dontAsk` (or use
`--permission-mode bypassPermissions`) and the same command executes the
tool. On Grok 0.2.x the original combination worked because the CLI
accepted `dontAsk` without enforcing it; the 0.2.39 embedded docs state
the flag takes effect only for `bypassPermissions` / always-approve.
**Paperclip version or commit**
master (917d2350f)
## What Changed
- `packages/adapters/grok-local/src/server/execute.ts`: `permissionMode`
no longer defaults to `dontAsk`. The adapter passes no
`--permission-mode` flag unless one is explicitly configured.
`--always-approve` (default on) remains the unattended policy.
- `packages/adapters/grok-local/src/index.ts`: config doc updated to
explain the new default and the Grok 1.0 semantics.
- `packages/adapters/grok-local/src/server/execute.test.ts`:
default-args assertion now requires the absence of `--permission-mode`;
new test covers explicit `permissionMode` pass-through.
## Verification
- `npx vitest run packages/adapters/grok-local` — 7 files, 29 tests, all
pass.
- `pnpm --filter @paperclipai/adapter-grok-local typecheck` — clean.
- Live matrix against Grok CLI 1.0.3 in a clean sandbox: `dontAsk
--always-approve` denies the first tool call; `--always-approve` alone
executes it; `bypassPermissions --always-approve` executes it; `dontAsk`
alone denies it.
## Risks
- Low risk. Operators who explicitly set `permissionMode` keep their
value verbatim. Only the implicit default changes, and the old default
is what breaks every run on current Grok CLI releases.
- On Grok 0.2.x the flag was unenforced, so omitting it does not change
behavior there.
## Model Used
Claude Fable 5 (`claude-fable-5`), extended thinking, tool use, via
Claude Code CLI.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
69590890d4
commit
fbd20b28d3
|
|
@ -26,7 +26,7 @@ Core fields:
|
|||
- instructionsFilePath (string, optional): absolute path to a markdown instructions file. Paperclip stages it into the execution workspace as \`Agents.md\` when safe, otherwise falls back to \`--rules @file\`
|
||||
- promptTemplate (string, optional): run prompt template
|
||||
- model (string, optional): Grok model id. Defaults to grok-build.
|
||||
- permissionMode (string, optional): Grok permission mode. Defaults to \`dontAsk\`
|
||||
- permissionMode (string, optional): Grok permission mode passed via \`--permission-mode\`. Unset by default: Grok >= 1.0 enforces \`dontAsk\` as deny-by-default and it overrides \`--always-approve\`, so unattended runs rely on \`--always-approve\` alone unless you explicitly need a mode
|
||||
- reasoningEffort (string, optional): Grok reasoning effort passed via \`--reasoning-effort\`
|
||||
- maxTurns (number, optional): maximum agent turns for the run
|
||||
- command (string, optional): defaults to "grok"
|
||||
|
|
|
|||
|
|
@ -71,10 +71,11 @@ describe("grok_local execute", () => {
|
|||
"--output-format",
|
||||
"streaming-json",
|
||||
"--always-approve",
|
||||
"--permission-mode",
|
||||
"dontAsk",
|
||||
]),
|
||||
);
|
||||
// Grok >= 1.0 enforces `dontAsk` as deny-by-default over --always-approve,
|
||||
// so no permission mode may be passed unless explicitly configured.
|
||||
expect(args).not.toContain("--permission-mode");
|
||||
expect(await fs.readFile(path.join(root, "Agents.md"), "utf8")).toContain("You are Grok.");
|
||||
expect(await pathExists(path.join(root, ".claude", "skills", "paperclip", "SKILL.md"))).toBe(true);
|
||||
await options.onLog?.("stdout", '{"type":"text","data":"done"}\n');
|
||||
|
|
@ -203,6 +204,42 @@ describe("grok_local execute", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("passes an explicitly configured permissionMode through to the CLI", async () => {
|
||||
let seenArgs: string[] = [];
|
||||
runProcessMock.mockImplementation(async (_runId, _target, _command, args) => {
|
||||
seenArgs = args;
|
||||
return {
|
||||
exitCode: 0,
|
||||
signal: null,
|
||||
timedOut: false,
|
||||
stdout: JSON.stringify({ type: "end", stopReason: "EndTurn", sessionId: "sess-1", requestId: "req-1" }),
|
||||
stderr: "",
|
||||
};
|
||||
});
|
||||
|
||||
const ctx: AdapterExecutionContext = {
|
||||
runId: "run-permission-mode",
|
||||
agent: {
|
||||
id: "agent-1",
|
||||
companyId: "company-1",
|
||||
name: "Grok Agent",
|
||||
adapterType: "grok_local",
|
||||
adapterConfig: {},
|
||||
},
|
||||
runtime: { sessionId: null, sessionParams: null, sessionDisplayId: null, taskKey: null },
|
||||
config: { cwd: await makeTempRoot(), permissionMode: "bypassPermissions" },
|
||||
context: {},
|
||||
authToken: "run-token",
|
||||
onLog: async () => {},
|
||||
};
|
||||
|
||||
await execute(ctx);
|
||||
|
||||
const flagIndex = seenArgs.indexOf("--permission-mode");
|
||||
expect(flagIndex).toBeGreaterThan(-1);
|
||||
expect(seenArgs[flagIndex + 1]).toBe("bypassPermissions");
|
||||
});
|
||||
|
||||
it("cleans up staged assets when setup fails before the Grok process starts", async () => {
|
||||
const root = await makeTempRoot();
|
||||
const instructionsPath = path.join(root, "managed", "AGENTS.md");
|
||||
|
|
|
|||
|
|
@ -203,7 +203,11 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
|||
);
|
||||
const command = asString(config.command, "grok");
|
||||
const model = asString(config.model, DEFAULT_GROK_LOCAL_MODEL).trim();
|
||||
const permissionMode = asString(config.permissionMode, "dontAsk").trim() || "dontAsk";
|
||||
// No default permission mode: Grok >= 1.0 enforces `dontAsk` as
|
||||
// deny-by-default and it overrides --always-approve, so passing it broke
|
||||
// every unattended run (the first tool call died with "User cancelled the
|
||||
// execution for tool ..."). --always-approve alone is the unattended policy.
|
||||
const permissionMode = asString(config.permissionMode, "").trim();
|
||||
const reasoningEffort = asString(config.reasoningEffort, "").trim();
|
||||
const maxTurns = asNumber(config.maxTurns, 0);
|
||||
const alwaysApprove = asBoolean(config.alwaysApprove, true);
|
||||
|
|
|
|||
Loading…
Reference in New Issue