The ~/.claude.json write was `printf ... > "$CLAUDE_JSON"`, which truncates before it writes. A write that fails part-way — a full home volume, which is the exact condition half this release exists to prevent — leaves the file unparseable, and it never self-heals: the next start's jq fails on the corrupt file, MERGED is empty, and the guard skips the write that would have repaired it. That file holds the OAuth account, so the failure mode is a permanently lost login, in service of a cosmetic flag that suppresses a tip. Demonstrated: old pattern loses the credential, new tmp+rename leaves the original intact. The correct pattern was already in triple-c-task-runner. The web terminal scoped its xterm key handler to Claude sessions but not its mobile input bar or its dedicated newline button, so both sent ESC+CR into `bash -l`, where readline has no binding for it. Silent no-op, and worse from a button that stays on screen looking live. Both now consult the active session's type, and the button is disabled with a reason on a shell tab. The Config tab claimed "Off overrides a global On" without qualification. True for the env-var-driven settings, false for TUI mode, Effort level and Focus mode, whose off state is *removing* a key — an older base image's entrypoint ignores the instruction to remove it. The copy now says so and points at the base-image update. HOW-TO-USE.md said there is no add-task form; AutomationTab renders a "New task" button. That file is fetched from GitHub at runtime by help_commands.rs, so the error was live in every user's Help dialog. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GBq2rGum6GX7xXgsas1fDc
132 lines
4.9 KiB
TypeScript
132 lines
4.9 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. Turning TUI mode, Effort level or Focus mode " +
|
|
"back to Global needs the container's base image updated first — those " +
|
|
"three 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 " +
|
|
"if one of them will not switch off."
|
|
}
|
|
>
|
|
<ClaudeCodeSettingsEditor
|
|
scope="project"
|
|
settings={project.claude_code_settings}
|
|
disabled={disabled}
|
|
disabledReason={disabledReason}
|
|
onSave={(settings) => save({ claude_code_settings: settings })}
|
|
/>
|
|
</ConfigGroup>
|
|
</>
|
|
);
|
|
}
|