Skip to content

Commit 47a6e4b

Browse files
committed
Env-level toggle for --dangerously-skip-permissions
Gate the blunt permission-bypass flag behind a per-environment setting so local/ssh envs don't default to "Claude can run anything." Defaults: daemon envs → bypass ON (throwaway VM, bounded blast radius) local / ssh / coder envs → bypass OFF (your own hardware) When bypass is OFF, autonomous tasks run with `--permission-mode acceptEdits` instead. That silences file edits but will pause on bash prompts / unknown MCP trust — the user answers from the task terminal's input field (already always-visible). - schema.ts: new `autonomous_bypass_permissions` boolean column (default false). Migration 0005. - POST /environments: defaults daemon=true, everything else=false; overridable via explicit body field. - PATCH /environments/:id: accepts updates to the flag. - agent.ts: picks --dangerously-skip-permissions vs --permission-mode acceptEdits based on env.autonomousBypassPermissions. - SettingsPanel: checkbox on each env card with a warning blurb explaining what "bypass" means for non-daemon envs (confirms via `confirm()` dialog before enabling on a local/ssh env). - SETUP.md: documents the one-time `claude` interactive approval needed on strict envs so the Supabase MCP trust prompt doesn't block autonomous runs. Chose not to commit `.claude/settings.json` — keeping the trust decision explicit per machine. Full backend suite: 81/81 green.
1 parent a41f243 commit 47a6e4b

10 files changed

Lines changed: 1483 additions & 16 deletions

File tree

apps/desktop/src/renderer/components/panels/SettingsPanel.tsx

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@ import {
2929
Copy,
3030
LogOut,
3131
} from 'lucide-react';
32-
import type { BacklogSource, BacklogItem, MarkdownFileBacklogConfig } from '@fastowl/shared';
32+
import type {
33+
BacklogSource,
34+
BacklogItem,
35+
Environment,
36+
MarkdownFileBacklogConfig,
37+
} from '@fastowl/shared';
3338
import { api, GitHubStatus, GitHubUser, GitHubRepo, WatchedRepo } from '../../lib/api';
3439
import { cn } from '../../lib/utils';
3540
import { Button } from '../ui/button';
@@ -648,10 +653,11 @@ function IntegrationsSettings() {
648653
}
649654

650655
function EnvironmentsSettings() {
651-
const { environments } = useWorkspaceStore();
656+
const { environments, setEnvironments } = useWorkspaceStore();
652657
const { deleteEnvironment, testConnection } = useEnvironmentActions();
653658
const [showAddModal, setShowAddModal] = useState(false);
654659
const [testing, setTesting] = useState<string | null>(null);
660+
const [togglingBypass, setTogglingBypass] = useState<string | null>(null);
655661

656662
const handleTest = async (envId: string) => {
657663
setTesting(envId);
@@ -668,6 +674,30 @@ function EnvironmentsSettings() {
668674
}
669675
};
670676

677+
const handleToggleBypass = async (env: Environment, next: boolean) => {
678+
// Extra friction for flipping a LOCAL env into "bypass everything"
679+
// mode — that's the your-whole-machine-is-at-stake branch.
680+
if (next && env.type !== 'daemon') {
681+
const ok = confirm(
682+
`Allow unattended Claude runs on "${env.name}" to bypass all permission prompts?\n\n` +
683+
`This environment is type "${env.type}" — autonomous tasks will be able to run any shell command, ` +
684+
`edit any file, and call any MCP tool WITHOUT asking you first. Use only if you trust the tasks ` +
685+
`that will run here (e.g., your own backlog against your own repo).\n\n` +
686+
`Recommended: keep this OFF for local / ssh envs; only enable for disposable daemon VMs.`
687+
);
688+
if (!ok) return;
689+
}
690+
setTogglingBypass(env.id);
691+
try {
692+
const updated = await api.environments.update(env.id, {
693+
autonomousBypassPermissions: next,
694+
} as unknown as Partial<Environment>);
695+
setEnvironments(environments.map((e) => (e.id === env.id ? updated : e)));
696+
} finally {
697+
setTogglingBypass(null);
698+
}
699+
};
700+
671701
return (
672702
<div className="space-y-6">
673703
<div className="flex items-center justify-between">
@@ -754,6 +784,27 @@ function EnvironmentsSettings() {
754784
{env.error}
755785
</div>
756786
)}
787+
<label className="flex items-start gap-2 mt-3 cursor-pointer">
788+
<input
789+
type="checkbox"
790+
checked={env.autonomousBypassPermissions}
791+
disabled={togglingBypass === env.id}
792+
onChange={(e) => void handleToggleBypass(env, e.target.checked)}
793+
className="mt-0.5"
794+
/>
795+
<div className="flex-1">
796+
<div className="text-sm font-medium">
797+
Allow unattended Claude runs to bypass permission prompts
798+
</div>
799+
<p className="text-xs text-muted-foreground mt-0.5">
800+
{env.type === 'daemon'
801+
? 'Recommended for throwaway daemon VMs — the blast radius is bounded to this machine.'
802+
: env.autonomousBypassPermissions
803+
? 'Enabled on a non-daemon env: Claude can run any shell command and edit any file on this machine during autonomous tasks.'
804+
: 'Off (recommended for non-daemon envs). Autonomous tasks use acceptEdits mode; they may pause on bash / MCP trust prompts — you can answer from the task terminal input below.'}
805+
</p>
806+
</div>
807+
</label>
757808
</div>
758809
<div className="flex items-center gap-2">
759810
<Button

docs/SETUP.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@ FastOwl spawns `claude` (interactive mode) via node-pty on the chosen environmen
3434

3535
Verify by running `claude --version` as the shell user FastOwl will use.
3636

37+
**One-time MCP trust approval** (only if you run autonomous tasks in **strict** mode on this environment — i.e., the env's "Allow unattended Claude runs to bypass permission prompts" toggle is OFF):
38+
39+
FastOwl's repo root ships a `.mcp.json` registering the Supabase MCP server. On first encounter, Claude Code prompts you to trust it. Autonomous runs can't answer that prompt, so do it once interactively:
40+
41+
```bash
42+
cd ~/path/to/fastowl # or wherever the clone lives on this env
43+
claude # opens the TUI
44+
# → prompted: "New MCP server found in .mcp.json: supabase — use this?"
45+
# → pick "Use this and all future MCP servers in this project"
46+
# → Ctrl-D to exit
47+
```
48+
49+
The approval lands in your user-level Claude config and sticks. You don't need to do this on daemon envs that have "bypass permissions" enabled — they skip all prompts by design.
50+
3751
### 3. GitHub OAuth app (already scaffolded in backend)
3852

3953
Used by Phase 6 integration (connect GitHub → PR monitoring, PR actions, repo listing).
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE "environments" ADD COLUMN "autonomous_bypass_permissions" boolean DEFAULT false NOT NULL;

0 commit comments

Comments
 (0)