feat(kickoff): add --permission-mode <mode> for finer-grained claude control (#603)#608
Merged
Merged
Conversation
…control (#603) Previously, `crosslink kickoff run` only exposed `--skip-permissions`, which always passed `--dangerously-skip-permissions` (full bypass) to the spawned claude session. This is appropriate for Docker-sandboxed kickoffs but more permissive than needed when running on the host (`--container none`). Claude itself supports a finer-grained `--permission-mode <mode>` flag (acceptEdits, auto, bypassPermissions, default, dontAsk, plan); `auto` in particular is what users want for unattended host runs — the permission classifier stays active for anomalous tool calls. This commit adds `--permission-mode <mode>` to `kickoff run` and `kickoff launch`, mutually exclusive with `--skip-permissions`, validated at clap parse time against claude's known set. Threading: - main.rs: KickoffCommands::{Run,Launch} gain the flag with `PossibleValuesParser` validation and `conflicts_with` for `skip_permissions`. The fallthrough `Launch` default in the bare `crosslink kickoff` wizard also gets `permission_mode: None`. - types.rs: KickoffOpts.permission_mode: Option<&str> - mod.rs: destructured in Run + Launch match arms, plumbed through dispatch_launch's signature, set in all three KickoffOpts construction sites (direct Run, dispatch_launch's --run path, wizard WizardStage::Run path) - run.rs: passes through to launch_local - launch.rs: launch_local signature gains `permission_mode`, forwards to build_agent_command. Resolution: permission_mode (when non-empty) wins over skip_permissions; empty string falls back to skip_permissions; neither = no flag. - plan.rs: plan mode passes `None` (plan mode never overrides perms). - swarm/lifecycle.rs, sentinel/engine.rs: KickoffOpts builders updated with `permission_mode: None` (internal callers, not user-facing). Usage: crosslink kickoff run "feature" --container none --permission-mode auto Tests: 2878 pass (+3 new): - test_build_agent_command_with_permission_mode_auto - test_build_agent_command_permission_mode_wins_over_skip_permissions - test_build_agent_command_empty_permission_mode_treated_as_none Existing 11 build_agent_command test calls + 14 KickoffOpts test constructions updated for the new field. Clippy `-D warnings -W clippy::unwrap_used -W clippy::expect_used` clean, fmt clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…h_local I added the attribute when extending `launch_local`'s signature for `permission_mode` (#603), but the original was already there. CI clippy catches it via `clippy::duplicated_attributes` (newly part of -D warnings on the CI rustc); my local rustc didn't flag it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes GH#603.
crosslink kickoff runpreviously only exposed--skip-permissions, which always passed--dangerously-skip-permissions(full bypass) to the spawned claude session. That's appropriate for Docker-sandboxed kickoffs but more permissive than needed when running on the host (--container none). Claude itself supports a finer-grained--permission-mode <mode>flag —autoin particular keeps the permission classifier active for anomalous tool calls while routine ones go through, which is what users typically want for unattended host runs.This PR adds
--permission-mode <mode>tokickoff runandkickoff launch, mutually exclusive with--skip-permissions, validated at clap parse time against claude's known set.Choice of approach
The issue offers two paths — a CLI flag (option A) or an env var (option B). Going with A for the cleaner long-term surface: validation happens at parse time, the option is discoverable via
--help, and it composes correctly withconflicts_withagainst the legacy--skip-permissions.Usage
Valid
<mode>values (enforced byclap::builder::PossibleValuesParser):acceptEdits,auto,bypassPermissions,default,dontAsk,planThreading
The flag walks down through every kickoff dispatch path:
main.rs—KickoffCommands::{Run, Launch}gain the field. The fallthroughLaunchdefault in the barecrosslink kickoffinteractive wizard also getspermission_mode: None.types.rs::KickoffOpts— newpermission_mode: Option<&'a str>field.mod.rs— destructured in bothRunandLaunchmatch arms, threaded throughdispatch_launch's signature, set in all threeKickoffOptsconstruction sites (directRun,dispatch_launch's--runpath, and the wizard'sWizardStage::Runpath).run.rs— passesopts.permission_modetolaunch_local.launch.rs—launch_localsignature gains the param;build_agent_commanddoes the resolution (see below).plan.rs— plan mode passesNone(plan mode never overrides permissions).swarm/lifecycle.rs,sentinel/engine.rs— internalKickoffOptsbuilders updated withpermission_mode: None(not user-facing).Container mode (
launch_container) does not usebuild_agent_command— it relies on the agent image's own entrypoint — so no plumbing there.Resolution semantics
Inside
build_agent_command:permission_mode(when non-empty) takes precedence overskip_permissions.permission_modeis treated asNoneand falls back to the legacy resolution.Some(...) + truecase unreachable from the public surface (conflicts_with), but the defense-in-depth ordering is tested anyway for internal callers.Test plan
cargo test --lib --bin crosslink— 2878 passed (+3 new)cargo clippy -- -D warnings -W clippy::unwrap_used -W clippy::expect_used— cleancargo fmt --all -- --check— cleantest_build_agent_command_with_permission_mode_auto— emits--permission-mode 'auto', no--dangerously-skip-permissionstest_build_agent_command_permission_mode_wins_over_skip_permissions— defense-in-depth precedence checktest_build_agent_command_empty_permission_mode_treated_as_none— empty string falls back to legacy resolutionbuild_agent_commandtest calls + 14KickoffOptstest constructions updated for the new fieldcrosslink kickoff run "feature" --doc design.md --issue 1 \ --container none --timeout 24h --permission-mode auto--permission-mode 'auto'and skip the bypass-permissions warning prompt.Out of scope
CROSSLINK_PERMISSION_MODEfor scripts/CI that don't want to rewrite CLI invocations, but keeping the surface area minimal here.permission_modeintolaunch_container. The container path uses the agent image's own permission setup, notbuild_agent_command.Cross-refs
--docdesign file #580 — kickoff macOS Keychain auth gap (closed).gitpointer unresolvable inside container + allowedTools blocks language build tools #584 — kickoff git-shim + allowedTools (closed)/designskill prelude (open)🤖 Generated with Claude Code