Saphan StudioDocs
Configuration

Which backends receive a grant

A permission grant class is your act; where it lands is a per-backend answer.

Setting workspace.worktree_grant_class is your act, and its effect depends on the backend the leg runs. A key whose effect differs per backend needs its answer written down per backend, and the engine holds that answer as one row per backend — never one path applied to all.

BackendReceives a grant?Where / why
claude-codeYes<worktree>/.claude/settings.json. A spawn starts in its working directory and the engine passes the agent no other settings location, so the agent finds a grant there and nowhere else.
codexNoThe engine governs codex's permission surface on its command line, under codex's strict-configuration mode, where a command-line key outranks every layer of codex's own configuration. There is no file left for the engine to compose.
qwen-codeNoqwen reads its own settings vocabulary, not a Claude Code settings document, and the only grant document the engine composes is the latter; copying it one directory over would not be a grant qwen honours. Give a qwen leg its tools on its command line (--allowed-tools) — which the engine does not compose either.
droidNoThe engine composes no grant document for it.
cursorNoThe engine composes no grant document for it — and runs on it are refused by the confinement layer; see the sandbox matrix below.
openai-compatNoNo vendor process runs on the seat at all — this backend is an HTTP request made by the engine, so there is no local program that could read a grant file.
agent-loopNosaphan-agent takes its whole mandate on its command line and the saphan configuration, and confines itself.
procNoA substrate binary is an arbitrary program you name, and the engine composes no vendor configuration for it.
anything elseRefusedA backend nobody has answered for does not get a silent default. Dispatch and worktree creation refuse by name.

⚠ A "No" row is not a statement about confinement. What bounds a leg that receives no grant is the confinement around it, which differs by backend and platform — read Sandboxing: which agent is confined, where before assuming either way.

Per-role classes

workspace.grant_class.<role> names a class for one fleet role — master, executor, review, design, scribe or advisor — and overrides workspace.worktree_grant_class for spawns carrying that --role. A role with no entry falls back to the workspace-wide class, and the fallback is recorded on the run.

The runbook: setting a grant class and confirming it reached the leg

  1. Write the class file. It is an ordinary Claude Code settings file. Only its permissions and env blocks are read; both are copied byte for byte, never re-authored. See Permission classes.
  2. Declare it.
    saphan config set workspace.worktree_grant_class /abs/path/to/worktree-toolchain-grant.settings.json
    A relative path resolves against the workspace root. If the deprecated run.grant is also set to a different path, this key wins and the dispatch says so.
  3. Dispatch, and read the composition's own answer. Whenever a declared grant is not provisioned for the leg's backend, the dispatch says so and quotes that backend's reason.
  4. Confirm on disk, for a claude-code leg only. After the worktree exists:
    cat <worktree>/.claude/settings.json
    git -C <worktree> status --porcelain
    The file carries your class's blocks, and git status is silent about it — the engine adds the destination to the repository's shared .git/info/exclude, so the copy never shows up as an untracked change in any worktree of that repo.
  5. For every other backend there is nothing to confirm on disk, and that is the correct outcome. Confirm the reason instead: it is in the dispatch's output and on the run record.

What to do when it refuses

  • grant-destination-undecided — the backend is known and no answer is declared for it. This is a gap in the engine, not your configuration, and no flag works around it. Report it.
  • grant-destination-unknown-backend — the name is not a backend. Check the spelling; editing the grant class will not help.
  • A leg reports permission denials and you expected a grant — check the backend first. On the "No" rows above no grant was ever composed, so a missing grant file is not what denied the leg; look at the confinement for that backend and platform.

If you want a file on a backend that receives none

You can always place one by hand:

saphan dispatch command … --provision /abs/path/to/class.settings.json=<dest>

⚠ Choosing <dest> there is your claim about that vendor, not the engine's. The engine declines to guess where another vendor's permission grant lands, and nobody has shown that a file placed this way behaves as a boundary for that vendor. Some vendors merge settings from several scopes or rewrite the settings file they are handed, which makes provision-then-verify unreliable.

On this page