fix(hub,cli): forward permissionMode on session resume#460
fix(hub,cli): forward permissionMode on session resume#460junmo-kim wants to merge 4 commits intotiann:mainfrom
Conversation
When a session is resumed, the cached permissionMode is now forwarded through the Hub → Runner → CLI pipeline via a new --permission-mode flag. Previously the mode was lost on resume, resetting to 'default'. Each CLI flavor validates the flag value against its own allowed permission modes (e.g. CLAUDE_PERMISSION_MODES) and rejects unknown values. The existing --yolo flag is preserved as a shorthand.
There was a problem hiding this comment.
Findings
- No actionable issues found in the added/modified lines.
Summary
- Review mode: initial. No actionable issues found in the latest diff. Residual risk: coverage only exercises hub-side forwarding for a Claude session; there is still no CLI parser or end-to-end resume coverage for the other agents touched here.
- Not found in repo/docs: tests covering the new
--permission-modeparsing incli/src/commands/*.
Testing
- Not run (automation).
bunis not available in this runner image, sobun test hub/src/sync/sessionModel.test.tscould not be executed.
HAPI Bot
Extract the CLI argument construction logic into a standalone exported function so it can be unit-tested independently. No behavior change.
Verify that the runner correctly forwards valid permission modes via --permission-mode, rejects invalid values, and falls back to --yolo when no permission mode is set.
There was a problem hiding this comment.
Findings
- [Major]
--permission-modedoes not actually take precedence over the legacy--yoloflag in the new CLI parsers. The runner helper emits only one flag (cli/src/runner/run.ts:860), but the direct entrypoints still overwriteoptions.permissionModewhen--yoloappears later, so commands likehapi --permission-mode default --yoloorhapi codex resume <id> --permission-mode read-only --yoloresume in the more permissive mode instead of the explicit override. Evidence:cli/src/commands/claude.ts:40,cli/src/commands/codex.ts:54,cli/src/commands/cursor.ts:38,cli/src/commands/gemini.ts:33,cli/src/commands/opencode.ts:32.
Suggested fix:let explicitPermissionMode: PermissionMode | undefined } else if (arg === '--permission-mode') { // validate mode explicitPermissionMode = parsedMode options.permissionMode = parsedMode } else if ((arg === '--yolo' || arg === LEGACY_ALIAS) && explicitPermissionMode === undefined) { options.permissionMode = LEGACY_MODE }
Summary
- Review mode: follow-up after new commits.
- 1 actionable issue found in the latest diff: the new CLI parsers do not enforce the advertised
--permission-modeprecedence over--yolo, which can resume a session with broader permissions than the explicit flag requested.
Testing
- Not run (automation).
bunis not available in this runner image, sobun test cli/src/runner/buildCliArgs.test.ts hub/src/sync/sessionModel.test.tscould not be executed.
HAPI Bot
cli/src/commands/claude.ts
Outdated
| throw new Error(`Invalid --permission-mode value: ${mode ?? '(missing)'}`) | ||
| } | ||
| options.permissionMode = mode as StartOptions['permissionMode'] | ||
| } else if (arg === '--yolo') { |
There was a problem hiding this comment.
[MAJOR] --permission-mode still loses to a later --yolo, so the precedence promised by this PR is not actually enforced. The runner helper now emits only one flag, but direct CLI invocations like hapi --permission-mode default --yolo or hapi codex resume <id> --permission-mode read-only --yolo will still end up in the legacy yolo mode because this branch overwrites the explicit selection. The same pattern exists in cli/src/commands/codex.ts:60, cli/src/commands/cursor.ts:44, cli/src/commands/gemini.ts:39, and cli/src/commands/opencode.ts:38.
Suggested fix:
let explicitPermissionMode: StartOptions['permissionMode'] | undefined
} else if (arg === '--permission-mode') {
// validate mode
explicitPermissionMode = mode as StartOptions['permissionMode']
options.permissionMode = explicitPermissionMode
} else if ((arg === '--yolo' || arg === '--dangerously-skip-permissions') && explicitPermissionMode === undefined) {
options.permissionMode = 'bypassPermissions'
unknownArgs.push('--dangerously-skip-permissions')
}When both flags are present, --permission-mode was silently overwritten by a later --yolo. Guard legacy flag branches with a hasExplicitPermissionMode check so the explicit flag wins.
There was a problem hiding this comment.
Findings
- [Major] The new resume path still drops
permissionModeafter a hub restart.hub/src/sync/syncEngine.ts:444now forwardssession.permissionMode, but that field is only rehydrated from the live cache inhub/src/sync/sessionCache.ts:143, and thesessionstable still has no persistedpermission_modecolumn inhub/src/store/index.ts:179. After any hub restart, resumed sessions will still come back indefault.
Summary
- Review mode: follow-up after new commits.
- 1 actionable issue found in the latest diff: resumed sessions only retain
permissionModeuntil the hub process restarts.
Testing
- Not run (security review; PR code treated as untrusted). The added test covers only the hot-cache path, not a store reload / hub restart path.
HAPI Bot
| resumeToken, | ||
| session.effort ?? undefined | ||
| session.effort ?? undefined, | ||
| session.permissionMode ?? undefined |
There was a problem hiding this comment.
[MAJOR] This fixes the hot-cache resume path, but it still drops permissionMode after a hub restart. resumeSession() now forwards session.permissionMode, yet SessionCache.refreshSession() rehydrates that field from existing?.permissionMode only, and the sessions table still does not persist it. Once the hub process restarts, session.permissionMode becomes undefined again and the original bug comes back on the next resume.
Suggested fix:
// persist permissionMode the same way model/effort are persisted
if (payload.permissionMode !== undefined && payload.permissionMode !== session.permissionMode) {
this.store.sessions.setSessionPermissionMode(payload.sid, payload.permissionMode, session.namespace, {
touchUpdatedAt: false
})
}
const session: Session = {
// ...
permissionMode: stored.permissionMode ?? existing?.permissionMode
}
Problem
When a session is resumed, the permission mode resets to
defaultregardless of what mode was active before. For example, a session running inbypassPermissions(yolo) mode loses that setting on resume.Solution
Forward the cached
permissionModethrough the Hub → Runner → CLI pipeline on resume via a new--permission-modeflag.resumeSession()readssession.permissionModefrom the in-memory cache and passes it tospawnSession()--permission-mode <mode>CLI argument (validated againstPERMISSION_MODES)CLAUDE_PERMISSION_MODES)--yolois preserved as a shorthand;--permission-modetakes precedence when both are presentOpen question: DB persistence
Currently
permissionModelives only in the Hub's in-memory session cache (populated via keepalive). This means it survives session resume but not Hub restarts — same ascollaborationModetoday.By contrast,
modelandeffortare persisted to thesessionstable (schema v5/v6) and survive Hub restarts.Would you prefer a follow-up to add
permission_modeto the DB (schema v7 migration), consistent with howmodel/effortare handled? Or is in-memory sufficient given that Hub restarts are relatively rare?Tests
sessionModel.test.ts: sets permissionMode via keepalive → ends session → resumes → verifies permissionMode is forwarded tospawnSession()