Problem
Two overlapping defects in how a permission mode is decided and represented.
1. The default has competing claimants. chatDefaults.permissionMode in the Runtime Host policy is already the authority — session-catalog-coordinator.ts:352 falls back to it whenever a client omits permissionMode. Three leftovers compete with it:
useNewTaskChoice holds the composer's new-task pick in a per-draft in-memory Map, so a user who switches to full access before starting a task finds it back on Auto next time. It is a shadow copy of a setting that already persists.
resolveCreateSessionInput re-derives the default in the desktop main process. Production only calls the synchronous resolveCreateSessionRequest; this function has no production caller and is kept alive by its own tests.
run-command-core.ts:317 sends parsed.options.yolo ? 'bypass' : 'ask', and runtime-host-session-driver.ts:182 defaults to 'ask' again. The CLI therefore always sends an explicit mode and can never inherit the configured default; --yolo reads as "yolo or ask" instead of a one-shot override.
The renderer also mirrors the default twice: app-shell.tsx:569 reads the selected Host's chatDefaults, app-shell.tsx:1309 reads a separate global copy hydrated from the default Host. With more than one Host connected the composer chip can show another Host's setting.
permission-mode-default.ts is not part of this: runtime-host-boot.ts:939 passes resolveDefaultPermissionMode to the skills IPC, where runtime-host-skills-ipc-main.ts:102 needs a concrete value to predict which mode a new session will start in and filter invocable skills accordingly. It stays.
2. execute is a permission mode with no behavior. Every production branch folds it back into ask:
permission-profile-compiler.ts:36 — case 'ask': case 'execute': compile to the same profile
settings.ts:680 — normalized to ask
permission-mode-menu.tsx:84 — displayed as ask
pi-tui-pickers.ts:716 — counted as Auto alongside ask
sandbox-boundary.ts:213 — genesis boundary takes the same branch as ask
executionBoundaryDisplayMode — the single source for what is actually in force — cannot return it at all. The only live writers are two subagent definitions (agent-catalog.ts:151, :176), where 'ask' is behaviorally identical. Five folding sites exist to neutralize one value.
Those writers are live, so stored execute values do exist: every Web Research or Implementation subagent spawn writes one.
Scope
Both halves change where a permission mode comes from and how it is spelled, and they touch the same CLI lines; they ship together.
Single authority
- Make the composer's new-task picker read and write
chatDefaults.permissionMode directly; drop the per-draft shadow state. In-session switching stays per-session.
- Delete
resolveCreateSessionInput and the tests covering it (resolveCreateSessionRequest and its tests stay).
- Let the CLI pass
undefined through when --yolo is absent, so Host chatDefaults applies. --yolo becomes a one-shot override.
- Collapse the two renderer mirrors onto the selected Host's value.
Removing execute
- Change the two
agent-catalog writers to 'ask' (behaviorally identical; stops producing new execute records).
- Add
decodePersistedPermissionMode in core, folding a stored 'execute' to 'ask', and use it at the four persistence decode sites: session header (session-store.ts:1098), agent run (agent-run.ts:600), subagent tool result (tool-result-record-schema.ts:308), scheduled task (scheduled-task.ts:505).
- Leave the other four
isPermissionMode callers as strict validation: the three protocol frame decoders and runtime-host-session-catalog-ipc-main.ts:154 check new input and wire values, which should reject execute outright once the epoch is bumped.
- Bump
RUNTIME_HOST_COMPATIBILITY_EPOCH to 30 with a note, so a client that still speaks execute is refused at the handshake instead of having a frame rejected mid-session.
- Remove
execute from PERMISSION_MODES and delete the five folding sites.
- Replace
LegacyPermissionMode with PermissionMode (identical member set).
- Delete
isPermissionModeWithinCeiling (no production consumer; its only caller is a test). This also retires the implicit "array order encodes privilege strength" contract on PERMISSION_MODES.
Explicitly not in scope: the permissionCeiling key stays in SUBAGENT_SESSION_RUNTIME_SHAPE. hasExactShape rejects unknown keys, so removing it would make existing subagent records fail to decode.
Rollout
Bumping the compatibility epoch refuses Client-Host pairs that have not upgraded together. That is the established mechanism for a wire semantics change (29 prior bumps are documented in protocol/index.ts), but it does mean a partially upgraded install cannot connect until both sides are updated.
Verification
- A new session starts in the configured default with no explicit client value, on desktop and on
maka run.
maka run --yolo still elevates; omitting it inherits the default rather than forcing ask.
- A stored session header, agent run, and subagent tool result carrying
permissionMode: 'execute' still load and present as Auto.
maka activate --permission-mode execute still works (kept as an input alias — it is a public subcommand, cli-core.ts:48, whose callers live outside this repo).
- A client at the previous compatibility epoch is refused at the handshake.
AI use
Generative tooling made a substantive contribution: Claude Code produced the audit that identified these defects and traced the demand chains. Findings were verified against current main sources by the human contributor before filing.
Problem
Two overlapping defects in how a permission mode is decided and represented.
1. The default has competing claimants.
chatDefaults.permissionModein the Runtime Host policy is already the authority —session-catalog-coordinator.ts:352falls back to it whenever a client omitspermissionMode. Three leftovers compete with it:useNewTaskChoiceholds the composer's new-task pick in a per-draft in-memory Map, so a user who switches to full access before starting a task finds it back on Auto next time. It is a shadow copy of a setting that already persists.resolveCreateSessionInputre-derives the default in the desktop main process. Production only calls the synchronousresolveCreateSessionRequest; this function has no production caller and is kept alive by its own tests.run-command-core.ts:317sendsparsed.options.yolo ? 'bypass' : 'ask', andruntime-host-session-driver.ts:182defaults to'ask'again. The CLI therefore always sends an explicit mode and can never inherit the configured default;--yoloreads as "yolo or ask" instead of a one-shot override.The renderer also mirrors the default twice:
app-shell.tsx:569reads the selected Host'schatDefaults,app-shell.tsx:1309reads a separate global copy hydrated from the default Host. With more than one Host connected the composer chip can show another Host's setting.permission-mode-default.tsis not part of this:runtime-host-boot.ts:939passesresolveDefaultPermissionModeto the skills IPC, whereruntime-host-skills-ipc-main.ts:102needs a concrete value to predict which mode a new session will start in and filter invocable skills accordingly. It stays.2.
executeis a permission mode with no behavior. Every production branch folds it back intoask:permission-profile-compiler.ts:36—case 'ask': case 'execute':compile to the same profilesettings.ts:680— normalized toaskpermission-mode-menu.tsx:84— displayed asaskpi-tui-pickers.ts:716— counted as Auto alongsideasksandbox-boundary.ts:213— genesis boundary takes the same branch asaskexecutionBoundaryDisplayMode— the single source for what is actually in force — cannot return it at all. The only live writers are two subagent definitions (agent-catalog.ts:151,:176), where'ask'is behaviorally identical. Five folding sites exist to neutralize one value.Those writers are live, so stored
executevalues do exist: every Web Research or Implementation subagent spawn writes one.Scope
Both halves change where a permission mode comes from and how it is spelled, and they touch the same CLI lines; they ship together.
Single authority
chatDefaults.permissionModedirectly; drop the per-draft shadow state. In-session switching stays per-session.resolveCreateSessionInputand the tests covering it (resolveCreateSessionRequestand its tests stay).undefinedthrough when--yolois absent, so HostchatDefaultsapplies.--yolobecomes a one-shot override.Removing
executeagent-catalogwriters to'ask'(behaviorally identical; stops producing newexecuterecords).decodePersistedPermissionModein core, folding a stored'execute'to'ask', and use it at the four persistence decode sites: session header (session-store.ts:1098), agent run (agent-run.ts:600), subagent tool result (tool-result-record-schema.ts:308), scheduled task (scheduled-task.ts:505).isPermissionModecallers as strict validation: the three protocol frame decoders andruntime-host-session-catalog-ipc-main.ts:154check new input and wire values, which should rejectexecuteoutright once the epoch is bumped.RUNTIME_HOST_COMPATIBILITY_EPOCHto 30 with a note, so a client that still speaksexecuteis refused at the handshake instead of having a frame rejected mid-session.executefromPERMISSION_MODESand delete the five folding sites.LegacyPermissionModewithPermissionMode(identical member set).isPermissionModeWithinCeiling(no production consumer; its only caller is a test). This also retires the implicit "array order encodes privilege strength" contract onPERMISSION_MODES.Explicitly not in scope: the
permissionCeilingkey stays inSUBAGENT_SESSION_RUNTIME_SHAPE.hasExactShaperejects unknown keys, so removing it would make existing subagent records fail to decode.Rollout
Bumping the compatibility epoch refuses Client-Host pairs that have not upgraded together. That is the established mechanism for a wire semantics change (29 prior bumps are documented in
protocol/index.ts), but it does mean a partially upgraded install cannot connect until both sides are updated.Verification
maka run.maka run --yolostill elevates; omitting it inherits the default rather than forcingask.permissionMode: 'execute'still load and present as Auto.maka activate --permission-mode executestill works (kept as an input alias — it is a public subcommand,cli-core.ts:48, whose callers live outside this repo).AI use
Generative tooling made a substantive contribution: Claude Code produced the audit that identified these defects and traced the demand chains. Findings were verified against current
mainsources by the human contributor before filing.