Repository navigation
Conversation
Previously, the mode pre-selected when approving a plan was either the sticky "last used" choice or a hardcoded fallback to "auto" — there was no way to pin a specific mode as the permanent default, so picking a different mode once (e.g. for one sensitive plan) silently became the default for every future approval. Adds `defaultPlanApprovalMode` to the settings store (default `"last_used"`, fully backward compatible), exposed as "Mode after plan approval" in Settings → General, right below "Initial task mode". `PlanApprovalSelector` now prefers this pinned mode over the remembered last-used mode when set. Since both the Claude and Codex adapters route their plan-approval prompt through the same shared `PlanApprovalSelector` UI, this covers both without touching `packages/agent` or `packages/core`. Generated-By: PostHog Code Task-Id: 602d18f7-fc49-4e98-865e-6cbf9e27a91f
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
|
React Doctor found no issues in the changed files. 🎉 Reviewed by React Doctor for commit |
Extract PlanApprovalSelector's mode-resolution chain into a standalone, testable `resolveInitialPlanApprovalMode` function, and derive the settings dropdown's mode options from a shared `PLAN_APPROVAL_MODES` list in `modeStyles.tsx` instead of hardcoding them separately in `GeneralSettings.tsx`. Generated-By: PostHog Code Task-Id: 602d18f7-fc49-4e98-865e-6cbf9e27a91f
🦔 ReviewHog reviewed this pull requestFound 0 must fix, 1 should fix, 0 consider. Published 1 finding (view the review). |
|
ReviewHog Alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏 |
There was a problem hiding this comment.
ReviewHog Report
Changes
Issues: 1 issue
Files (4)
packages/ui/src/features/permissions/PlanApprovalSelector.tsxpackages/ui/src/features/sessions/modeStyles.tsxpackages/ui/src/features/settings/sections/GeneralSettings.tsxpackages/ui/src/features/settings/settingsStore.ts
|
|
||
| // Concrete modes `buildExitPlanModePermissionOptions` (packages/agent) can | ||
| // offer when a plan is approved, excluding "plan" itself. Kept here, next to | ||
| // the label map settings/approval UI already share, so both pick modes from | ||
| // one list. | ||
| export const PLAN_APPROVAL_MODES: readonly ExecutionMode[] = [ | ||
| "auto", | ||
| "default", | ||
| "acceptEdits", | ||
| "bypassPermissions", | ||
| ]; |
There was a problem hiding this comment.
PLAN_APPROVAL_MODES hardcodes Claude-only mode ids, breaking the pinned setting for the Codex adapter
Why we think it's a valid issue
- Checked: PR-head
PLAN_APPROVAL_MODES(modeStyles.tsx:75-80) and itsMODE_LABELS/MODE_STYLESentries forfull-access(lines 42-43, 68); the pinned branchif (defaultApprovalMode !== "last_used" && has(defaultApprovalMode))inresolveInitialPlanApprovalMode(PlanApprovalSelector.tsx:48-50); Claude'sbuildExitPlanModePermissionOptions(permission-options.ts:107-136); and Codex'srequestPlanImplementation(codex-app-server-agent.ts:1063-1099). - Found: Codex offers exactly
autoplus (ALLOW_BYPASS-gated)full-access(codex-app-server-agent.ts:1078-1092) — neverdefault/acceptEdits/bypassPermissions. So for a Codex sessionhas()is false for three of the four picklist entries, and the pinned branch falls through to the last-used/auto chain — the pin silently no-ops.full-accessis a validExecutionModewith existing label/style entries but is absent fromPLAN_APPROVAL_MODES, so Codex's one meaningful elevated mode is unpinnable. The picker is rendered globally in General settings (unconditionalPLAN_APPROVAL_MODES.map), not adapter-scoped, so Codex-only users see dead options. - Impact: A real, reproducible defect directly from this PR: concrete trigger (Codex session + pin any of three Claude-only modes → no-op; or want to pin
full-access→ impossible) and concrete consequence (explicit user preference silently ignored; relevant mode unreachable). Contradicts the PR's stated Codex coverage. Clears the keep bar — not speculative, not style, premise verified. - Priority: Lowering must_fix → should_fix. It fails safe: the no-op degrades to the exact prior last-used/auto behavior (no dangerous or data-losing action), the default
last_usedis backward compatible so untouched users are unaffected, and only Codex users who actively select a Claude-only mode are hit. A completeness gap in a new convenience feature, not a regression, crash, security, or data-loss bug — meaningful to fix but not release-blocking.
Issue description
PLAN_APPROVAL_MODES is ["auto", "default", "acceptEdits", "bypassPermissions"], and GeneralSettings.tsx uses exactly this list to populate the new "Mode after plan approval" picker. But the two adapters that route through PlanApprovalSelector offer completely different approve-option sets: Claude's buildExitPlanModePermissionOptions (packages/agent/src/adapters/claude/permissions/permission-options.ts) offers bypassPermissions/auto/acceptEdits/default, while Codex's requestPlanImplementation (packages/agent/src/adapters/codex-app-server/codex-app-server-agent.ts:1063-1099) only ever offers auto and full-access (gated by ALLOW_BYPASS) — never default, acceptEdits, or bypassPermissions. In resolveInitialPlanApprovalMode (PlanApprovalSelector.tsx), the pinned-mode branch is if (defaultApprovalMode !== "last_used" && has(defaultApprovalMode)). For a Codex session, has() will be false for default/acceptEdits/bypassPermissions (they're never in approveOptions), so pinning to any of those three Settings choices silently falls through to the old last-used/auto fallback chain — a no-op that contradicts what the user configured. Worse, Codex's actual bypass-equivalent mode, "full-access", isn't in PLAN_APPROVAL_MODES at all, so a Codex user can never pin the one mode that would be meaningful for them, even though "full-access" is a valid ExecutionMode (packages/shared/src/exec-types.ts) and already has an entry in this same file's MODE_LABELS/MODE_STYLES maps. This directly contradicts the PR's stated claim that "both the Claude and Codex adapters route their plan-approval prompt through this same shared UI component, [so] the change covers both." In practice, for Codex users the setting only works for the "auto" choice; the other three choices are dead options and the relevant elevated-trust mode is unreachable.
Suggested fix
Make the offered pin choices adapter-agnostic and derived from what can actually be offered, e.g. include "full-access" alongside the Claude modes (PLAN_APPROVAL_MODES = ["auto", "default", "acceptEdits", "bypassPermissions", "full-access"]), or better, have GeneralSettings.tsx/resolveInitialPlanApprovalMode only surface/select from modes that are relevant per-adapter. At minimum, add "full-access" to the list so Codex users have a working pinned option, and document (or filter) that default/acceptEdits/bypassPermissions are Claude-only and will silently no-op under Codex.
Prompt to fix with AI (copy-paste)
## Context
@packages/ui/src/features/sessions/modeStyles.tsx#L70-80
<issue_description>
`PLAN_APPROVAL_MODES` is `["auto", "default", "acceptEdits", "bypassPermissions"]`, and `GeneralSettings.tsx` uses exactly this list to populate the new "Mode after plan approval" picker. But the two adapters that route through `PlanApprovalSelector` offer completely different approve-option sets: Claude's `buildExitPlanModePermissionOptions` (packages/agent/src/adapters/claude/permissions/permission-options.ts) offers `bypassPermissions`/`auto`/`acceptEdits`/`default`, while Codex's `requestPlanImplementation` (packages/agent/src/adapters/codex-app-server/codex-app-server-agent.ts:1063-1099) only ever offers `auto` and `full-access` (gated by `ALLOW_BYPASS`) — never `default`, `acceptEdits`, or `bypassPermissions`. In `resolveInitialPlanApprovalMode` (PlanApprovalSelector.tsx), the pinned-mode branch is `if (defaultApprovalMode !== "last_used" && has(defaultApprovalMode))`. For a Codex session, `has()` will be false for `default`/`acceptEdits`/`bypassPermissions` (they're never in `approveOptions`), so pinning to any of those three Settings choices silently falls through to the old last-used/auto fallback chain — a no-op that contradicts what the user configured. Worse, Codex's actual bypass-equivalent mode, `"full-access"`, isn't in `PLAN_APPROVAL_MODES` at all, so a Codex user can never pin the one mode that would be meaningful for them, even though `"full-access"` is a valid `ExecutionMode` (packages/shared/src/exec-types.ts) and already has an entry in this same file's `MODE_LABELS`/`MODE_STYLES` maps. This directly contradicts the PR's stated claim that "both the Claude and Codex adapters route their plan-approval prompt through this same shared UI component, [so] the change covers both." In practice, for Codex users the setting only works for the "auto" choice; the other three choices are dead options and the relevant elevated-trust mode is unreachable.
</issue_description>
<issue_validation>
- **Checked:** PR-head `PLAN_APPROVAL_MODES` (modeStyles.tsx:75-80) and its `MODE_LABELS`/`MODE_STYLES` entries for `full-access` (lines 42-43, 68); the pinned branch `if (defaultApprovalMode !== "last_used" && has(defaultApprovalMode))` in `resolveInitialPlanApprovalMode` (PlanApprovalSelector.tsx:48-50); Claude's `buildExitPlanModePermissionOptions` (permission-options.ts:107-136); and Codex's `requestPlanImplementation` (codex-app-server-agent.ts:1063-1099).
- **Found:** Codex offers exactly `auto` plus (ALLOW_BYPASS-gated) `full-access` (codex-app-server-agent.ts:1078-1092) — never `default`/`acceptEdits`/`bypassPermissions`. So for a Codex session `has()` is false for three of the four picklist entries, and the pinned branch falls through to the last-used/auto chain — the pin silently no-ops. `full-access` is a valid `ExecutionMode` with existing label/style entries but is absent from `PLAN_APPROVAL_MODES`, so Codex's one meaningful elevated mode is unpinnable. The picker is rendered globally in General settings (unconditional `PLAN_APPROVAL_MODES.map`), not adapter-scoped, so Codex-only users see dead options.
- **Impact:** A real, reproducible defect directly from this PR: concrete trigger (Codex session + pin any of three Claude-only modes → no-op; or want to pin `full-access` → impossible) and concrete consequence (explicit user preference silently ignored; relevant mode unreachable). Contradicts the PR's stated Codex coverage. Clears the keep bar — not speculative, not style, premise verified.
- **Priority:** Lowering must_fix → should_fix. It fails safe: the no-op degrades to the exact prior last-used/auto behavior (no dangerous or data-losing action), the default `last_used` is backward compatible so untouched users are unaffected, and only Codex users who actively select a Claude-only mode are hit. A completeness gap in a new convenience feature, not a regression, crash, security, or data-loss bug — meaningful to fix but not release-blocking.
</issue_validation>
## Task
Investigate the issue and solve it
<potential_solution>
Make the offered pin choices adapter-agnostic and derived from what can actually be offered, e.g. include `"full-access"` alongside the Claude modes (`PLAN_APPROVAL_MODES = ["auto", "default", "acceptEdits", "bypassPermissions", "full-access"]`), or better, have `GeneralSettings.tsx`/`resolveInitialPlanApprovalMode` only surface/select from modes that are relevant per-adapter. At minimum, add `"full-access"` to the list so Codex users have a working pinned option, and document (or filter) that `default`/`acceptEdits`/`bypassPermissions` are Claude-only and will silently no-op under Codex.
</potential_solution>
Prompt To Fix All With AIFix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
packages/ui/src/features/permissions/PlanApprovalSelector.tsx:113-121
**Resolved Mode Leaves State Stale**
When persisted settings hydrate after mount, or the mounted selector receives a new approval with different options, `initialMode` recomputes but the following `useState(initialMode)` retains the old selection. Approving can therefore submit the pre-hydration mode or a mode that is not offered by the current approval request.
### Issue 2 of 2
packages/ui/src/features/sessions/modeStyles.tsx:75-80
**Codex Full Access Is Missing**
Codex offers `full-access` after plan approval when bypass is allowed, but this list controls the new settings menu and omits that mode. Codex users can select Full access during approval but cannot pin it as their default.
```suggestion
export const PLAN_APPROVAL_MODES: readonly ExecutionMode[] = [
"auto",
"default",
"acceptEdits",
"bypassPermissions",
"full-access",
];
```
Reviews (1): Last reviewed commit: "Simplify plan-approval mode resolution a..." | Re-trigger Greptile |
| const initialMode = useMemo( | ||
| () => | ||
| resolveInitialPlanApprovalMode( | ||
| approveOptions, | ||
| defaultApprovalMode, | ||
| lastApprovalMode, | ||
| ), | ||
| [approveOptions, lastApprovalMode, defaultApprovalMode], | ||
| ); |
There was a problem hiding this comment.
Resolved Mode Leaves State Stale
When persisted settings hydrate after mount, or the mounted selector receives a new approval with different options, initialMode recomputes but the following useState(initialMode) retains the old selection. Approving can therefore submit the pre-hydration mode or a mode that is not offered by the current approval request.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/ui/src/features/permissions/PlanApprovalSelector.tsx
Line: 113-121
Comment:
**Resolved Mode Leaves State Stale**
When persisted settings hydrate after mount, or the mounted selector receives a new approval with different options, `initialMode` recomputes but the following `useState(initialMode)` retains the old selection. Approving can therefore submit the pre-hydration mode or a mode that is not offered by the current approval request.
How can I resolve this? If you propose a fix, please make it concise.| export const PLAN_APPROVAL_MODES: readonly ExecutionMode[] = [ | ||
| "auto", | ||
| "default", | ||
| "acceptEdits", | ||
| "bypassPermissions", | ||
| ]; |
There was a problem hiding this comment.
Codex offers full-access after plan approval when bypass is allowed, but this list controls the new settings menu and omits that mode. Codex users can select Full access during approval but cannot pin it as their default.
| export const PLAN_APPROVAL_MODES: readonly ExecutionMode[] = [ | |
| "auto", | |
| "default", | |
| "acceptEdits", | |
| "bypassPermissions", | |
| ]; | |
| export const PLAN_APPROVAL_MODES: readonly ExecutionMode[] = [ | |
| "auto", | |
| "default", | |
| "acceptEdits", | |
| "bypassPermissions", | |
| "full-access", | |
| ]; |
Rule Used: When implementing new features, ensure that the UI... (source)
Learned From
PostHog/posthog#32595
PostHog/posthog#32677
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/ui/src/features/sessions/modeStyles.tsx
Line: 75-80
Comment:
**Codex Full Access Is Missing**
Codex offers `full-access` after plan approval when bypass is allowed, but this list controls the new settings menu and omits that mode. Codex users can select Full access during approval but cannot pin it as their default.
```suggestion
export const PLAN_APPROVAL_MODES: readonly ExecutionMode[] = [
"auto",
"default",
"acceptEdits",
"bypassPermissions",
"full-access",
];
```
**Rule Used:** When implementing new features, ensure that the UI... ([source](https://app.greptile.com/posthog-org-19734/-/custom-context?memory=5d57f0af-0be1-44de-8885-055f27e2885f))
**Learned From**
[PostHog/posthog#32595](https://github.com/PostHog/posthog/pull/32595)
[PostHog/posthog#32677](https://github.com/PostHog/posthog/pull/32677)
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
|
I didn't realize the current behavior is supposed to be last used by default. I think there's a bug in that. If we fix that bug, I don't think we need this PR. |
Problem
Approving a plan pre-selects a continue-mode that's either a sticky "last used" choice or a hardcoded "auto" fallback — there's no way to pin a specific mode as the permanent default. Pick a different mode once (e.g. for one sensitive plan), and every future plan approval silently defaults to that mode too.
Changes
defaultPlanApprovalModeto the settings store (packages/ui/src/features/settings/settingsStore.ts), default"last_used"(fully backward compatible).GeneralSettings.tsx).PlanApprovalSelectornow resolves its pre-selected mode from this pinned setting first, falling back to the existing remembered/last-used chain when it's"last_used"or the pinned mode isn't offered.packages/agentorpackages/core.How did you test this?
pnpm --filter @posthog/ui typecheckandpnpm --filter @posthog/ui test(added cases tosettingsStore.test.tsandPlanApprovalSelector.test.tsxcovering persistence round-trip and the pinned-mode-overrides-remembered-mode / pinned-mode-not-offered branches) — all passing.biome checkon the changed files.Automatic notifications
Created with PostHog Code