Build App (Preview) / compute-version (pull_request) Successful in 3s
Build Container / build-container (pull_request) Successful in 10m5s
Build App (Preview) / create-release (pull_request) Successful in 1s
Build App (Preview) / build-macos (pull_request) Successful in 4m31s
Build App (Preview) / build-linux (pull_request) Successful in 5m21s
Build App (Preview) / build-windows (pull_request) Successful in 19m1s
Build App (Preview) / prune-previews (pull_request) Successful in 1s
Docs and disclosure. HOW-TO-USE.md's settings table still described the pre-fix behaviour — and help_commands.rs fetches that file from GitHub main at runtime, ahead of the embedded copy, so it would have reached every user's Help dialog the moment this merged. The Config tab named three settings that need a base-image update; there are four, and the omitted one (Session recap) is the one that fails *without* the "won't switch off" symptom the warning teaches. Both now also state the cost nobody had written down: changing any of these recreates the container, which commits a layer. Two stale comments that told a reviewer the code was safe when it was not. compute_claude_code_settings_fingerprint still claimed the historical fingerprint is preserved so an upgrade cannot churn every container — carried over from before the widening, false since the format string changed. And capabilities/default.json, which is the reviewed threat model of record, described a "Save to host…" action this branch deletes. Security and correctness. update_settings validated env vars and nothing else, so the *global* default_ssh_key_path — the fallback for every project without an override — took `/` and read-only bind-mounted the host, which entrypoint.sh then copies into the home volume. classify_ mount_source ran canonicalize on the raw string, which resolves a relative path against Triple-C's own cwd, so `.` and `..` were accepted or refused depending on where the app was launched; the daemon then refuses the mount and the project can never start. Its test passed only because its examples did not exist under app/src-tauri. bind_mount_exclusions still derived a path from every row while project_path_mounts had learned to skip unmountable ones, so a legacy row made /workspace/<name> ordinary container content that a migration would then exclude from staging and destroy. The skip is also logged now rather than silently dropping a folder. The terminal's file-in path checked is_dir() but not file type, so a dropped FIFO blocked forever with no timeout — and it is the only route in now. The web terminal labelled sessions from a global set at request time, so two quick opens swapped them; harmless until Shift+Enter became type-dependent, at which point a mislabelled Claude session submitted a half-written prompt. Opened now carries the type. Every ~/.claude.json write goes through one atomic helper. The awsAuthRefresh branches still truncated in place — the same corruption the Shift+Enter block was fixed for twenty lines later, and its own comment said so. Demonstrated: a failed write now leaves the original byte-identical. And the registration test I added yesterday could pass while the property was false: an audit got five real unregistered commands past its exact-string attribute match, and "exactly once" was in its name but not its body. Mutation-checked against all six shapes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GBq2rGum6GX7xXgsas1fDc
134 lines
5.1 KiB
TypeScript
134 lines
5.1 KiB
TypeScript
import type { Project } from "../../../../lib/types";
|
|
import Toggle from "../../../ui/Toggle";
|
|
import { ConfigGroup, SwitchRow } from "../../../ui/Field";
|
|
import PermissionModeControl, { permissionModePatch } from "../../PermissionModeControl";
|
|
import ClaudeInstructionsEditor from "../../ClaudeInstructionsEditor";
|
|
import ClaudeCodeSettingsEditor from "../../ClaudeCodeSettingsEditor";
|
|
import AuthBridgeRow from "./AuthBridgeRow";
|
|
|
|
interface Props {
|
|
project: Project;
|
|
save: (patch: Partial<Project>) => Promise<boolean>;
|
|
disabled: boolean;
|
|
disabledReason?: string;
|
|
}
|
|
|
|
export default function RuntimeSection({
|
|
project,
|
|
save,
|
|
disabled,
|
|
disabledReason,
|
|
}: Props) {
|
|
return (
|
|
<>
|
|
<ConfigGroup
|
|
title="Runtime"
|
|
description="How much the sandbox lets Claude do, and what contains it."
|
|
>
|
|
<div className="pb-2 border-b border-[var(--border-color)]">
|
|
<PermissionModeControl
|
|
project={project}
|
|
onChange={(mode) => save(permissionModePatch(mode))}
|
|
/>
|
|
</div>
|
|
|
|
<SwitchRow
|
|
label="Sandbox mode"
|
|
hint="Claude Code's bash sandbox (bubblewrap filesystem and network isolation). Triple-C is the source of truth: toggling this overrides any manual /sandbox configuration in the container's settings.json on next start."
|
|
control={
|
|
<Toggle
|
|
label="Sandbox mode"
|
|
checked={project.sandbox_mode_enabled}
|
|
disabled={disabled}
|
|
onChange={(v) => save({ sandbox_mode_enabled: v })}
|
|
/>
|
|
}
|
|
/>
|
|
|
|
<SwitchRow
|
|
label="Allow container spawning"
|
|
hint="Mounts the Docker socket so Claude can build and run Docker containers from inside the sandbox."
|
|
control={
|
|
<Toggle
|
|
label="Allow container spawning"
|
|
checked={project.allow_docker_access}
|
|
disabled={disabled}
|
|
onChange={(v) => save({ allow_docker_access: v })}
|
|
/>
|
|
}
|
|
/>
|
|
|
|
<SwitchRow
|
|
label="VPN support"
|
|
hint="Grants NET_ADMIN and the /dev/net/tun device so a VPN client (PIA, WireGuard, OpenVPN) can build a tunnel inside the container. Without it a client installs and runs but its connection hangs until it times out. Anything in the container can then reconfigure the container's own network stack; the host's is untouched. Changing this recreates the container on its next start — the home and .claude volumes are preserved."
|
|
control={
|
|
<Toggle
|
|
label="VPN support"
|
|
checked={project.vpn_support_enabled}
|
|
disabled={disabled}
|
|
onChange={(v) => save({ vpn_support_enabled: v })}
|
|
/>
|
|
}
|
|
/>
|
|
|
|
{/* Not gated on `disabled`: the bridge is host-side and has its own
|
|
command, so it can be switched on while a login is hanging — which
|
|
is the only moment anyone reaches for it. It owns its state rather
|
|
than going through `save`. */}
|
|
<AuthBridgeRow project={project} />
|
|
|
|
<SwitchRow
|
|
label="Mission Control"
|
|
hint="A web dashboard for monitoring and managing Claude sessions remotely."
|
|
control={
|
|
<Toggle
|
|
label="Mission Control"
|
|
checked={project.mission_control_enabled}
|
|
disabled={disabled}
|
|
onChange={(v) => save({ mission_control_enabled: v })}
|
|
/>
|
|
}
|
|
/>
|
|
|
|
{disabled && disabledReason && (
|
|
<p className="text-xs text-[var(--text-disabled)]">{disabledReason}</p>
|
|
)}
|
|
</ConfigGroup>
|
|
|
|
<ConfigGroup
|
|
title="Claude instructions"
|
|
description="Written to ~/.claude/CLAUDE.md inside this project's container."
|
|
>
|
|
<ClaudeInstructionsEditor
|
|
instructions={project.claude_instructions ?? ""}
|
|
disabled={disabled}
|
|
disabledReason={disabledReason}
|
|
onSave={(value) => save({ claude_instructions: value || null })}
|
|
/>
|
|
</ConfigGroup>
|
|
|
|
<ConfigGroup
|
|
title="Claude Code settings"
|
|
description={
|
|
"Per-project CLI behaviour. Anything left on Global follows Settings; " +
|
|
"Off overrides a global On. Changing any of these recreates the container, " +
|
|
"which commits a new image layer — so flipping switches repeatedly costs disk. " +
|
|
"Turning TUI mode, Effort level, Focus mode or Session recap back to Global " +
|
|
"also needs the base image updated first: those four are cleared by removing a " +
|
|
"key, and an older image's startup script ignores the instruction to remove it. " +
|
|
"Update the base image from Overview. TUI mode, Effort level and Focus mode " +
|
|
"visibly refuse to switch off until you do; Session recap just stays off silently."
|
|
}
|
|
>
|
|
<ClaudeCodeSettingsEditor
|
|
scope="project"
|
|
settings={project.claude_code_settings}
|
|
disabled={disabled}
|
|
disabledReason={disabledReason}
|
|
onSave={(settings) => save({ claude_code_settings: settings })}
|
|
/>
|
|
</ConfigGroup>
|
|
</>
|
|
);
|
|
}
|