Worktree sessions: inherit parent's always-allow permissions #101
Labels
No labels
backend
bug
chore
duplication
effort:complex
effort:medium
effort:trivial
enhancement
frontend
fullstack
priority:high
ready-for-agent
refactor
security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
dries/ocman#101
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Worktree sessions launched from a parent OpenCode session (via MCP
split_to_worktreeor the/wtUI flow) start with no knowledge of the parent's runtime "Allow always" approvals. The user is re-prompted for every pattern the parent already accepted.OpenCode keeps "Allow always" state in-memory in the parent process and exposes no API to read it. ocman, however, already records every judge-approved permission's patterns in the
auto_approved_permissiontable. By extending that capture to also include user-clicked "Allow always" replies, ocman has a near-complete shadow of the parent's runtime allowlist — and can replay it into the child viaOPENCODE_PERMISSION.Goal
Worktree sessions launched from a parent session inherit the parent's accumulated always-allow patterns at split time. Default-on Settings toggle. Soft-fail on any error (log + result-payload note, never block launch).
Scope
split_to_worktree/wtUI flow (POST /api/worktree/create-and-launch)split_to_session(same cwd, already inherits project config via cwd-walk)Allow oncerepliesApproach in one paragraph
OpenCode permissions config is loaded with
OPENCODE_PERMISSION(inline JSON) taking precedence over project/global config. We pass that env var through tmux's-e KEY=VALUEflag at child launch. The value is a permission object synthesised from the parent session'sauto_approved_permissionrows: judge-approved patterns (already captured) plus user-clicked "Allow always" patterns (new capture in this issue). The child OpenCode loads it as policy — effectively replaying the parent's runtime allowlist.Implementation steps
1. Capture user-clicked "Allow always" approvals
internal/server/autoapprove.go+autoapprove_watcher.gomap[sessionID|permissionID] -> {permission, patterns}populated onpermission.asked, evicted onpermission.replied.permission.repliedwithreply == "always", callstateDB.RecordApprovedPermissionwithReasoning = "user clicked Allow always",JudgeSessionID = "".autoapprove_test.go: asked->replied("always") writes a row; "once"/"reject" do not.2. New package
internal/permissions/inherit.goBuildInheritedPermissionsJSON(lister, platform, parentSessionID) (json string, count int, err error).PermissionTextagainst the OpenCode allowlist:read, edit, glob, grep, list, bash, task, external_directory, todowrite, question, webfetch, websearch, repo_clone, repo_overview, lsp, doom_loop, skill.todowrite, question, webfetch, websearch, doom_loop) ->"<key>": "allow".{"*": "ask", <dedup allow patterns>...}. OpenCode evaluates the last matching rule, so*: askfirst, allows last.("", 0, nil).3. tmux env-var plumbing
internal/server/tmux.go+ teststmuxRunnercallbacks withenv map[string]string:newSession(name, directory string, env map[string]string, command string) errornewWindow(name, directory string, env map[string]string, command string) errornewNamedWindow(sessionName, windowName, directory string, env map[string]string, command string) error-e KEY=VALUEargs between-c <dir>and the command.^[A-Z_][A-Z0-9_]*$); reject NUL/newline in values.nil) and test fakes intmux_idempotent_test.go.-e OPENCODE_PERMISSION=...reaches the runner.4. Launcher accepts inherited env
internal/mcp/launcher.goInheritedEnv map[string]stringtoLaunchRequest.TmuxLaunchertofunc(projectDir, worktreeDir string, env map[string]string) (target string, launched bool, err error).LaunchWithWorktreeforwardsreq.InheritedEnv.server.gowiring (threeNewSessionLaunchercall sites) andlauncher_test.gofakes.5. Setting (no schema migration — reuse generic
settingKV table from v12)internal/state/db.goworktree.inherit_permissions, values"1"/"0", default"1"when row absent.GetWorktreeInheritPermissions() (bool, error)+SetWorktreeInheritPermissions(bool) error.GET/POST /api/settings/worktree-inherit-permissions, wired inserver.gomux.6. Wire MCP
split_to_worktreeinternal/mcp/tools_split.goGetSession, if setting on, callBuildInheritedPermissionsJSON.req.InheritedEnv["OPENCODE_PERMISSION"] = json.permissionsInherited(bool)permissionsInheritedCount(int)permissionsInheritError(string, only on failure)tools_test.go.7. Wire
/wtUI flowBackend —
internal/server/handlers_worktree.goparentSessionIdin request body.parentSessionId != "", build JSON via the same helper.permissionsInherited/permissionsInheritedCount/permissionsInheritErrorto response JSON.handlers_worktree_test.go.Frontend
frontend/src/lib/api.types.ts: addparentSessionId?: stringtoWorktreeCreateRequest; add the three new fields toWorktreeCreateResponse.frontend/src/components/WorktreeFormModal.tsx:sessionIdprop (audit all call sites: command-palette/wt, per-project Worktrees view).parentSessionId..lengthclient-side.api.worktree.test.ts, modal hint rendering, settings panel.8. Docs
AGENTS.md: short note under MCP server and Worktrees sections..opencode/skills/ocman-session-splitting/SKILL.md: agent should mentionpermissionsInherited{,Count}in its summary.Verification
After each phase:
make lint && make testbash "git *"pattern in a parent session -> spawn worktree -> child does not prompt forgit status.Risks
OPENCODE_PERMISSIONexceeds OS env-var limit on highly active sessionsrepliedpermissionJSON schema driftPermissionTextparser misidentifies key-equoting edge casesexec.Commandargv (no shell interpolation); document min tmux version