Repository navigation
Conversation
- Enter, Shift+Enter and Command+Enter reach Claude Code as distinct sequences, as they already do for Codex. - Command+A selects the whole Claude draft (shown as a layer over the prompt rows); Command+C/X copy/cut it, typing, paste or Backspace replace or clear it. Actions run through Claude's Ctrl+G external editor round trip with a bundled $VISUAL helper; input is held while it runs and a Ctrl+G picked up late is cancelled, never opening an editor. - macOS launches without any locale get LC_CTYPE=UTF-8 so pbcopy and friends stop turning Cyrillic into mojibake. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Requests are files req.<n> claimed or cancelled by an atomic rename, so a Ctrl+G Claude never handles no longer blocks later actions and a request can't be both acted on and cancelled. - A session's actions run one after another; input typed meanwhile keeps its place, and held input still settles answered prompts. - Only a Claude launched with the helper (SessionMetadata.claudeDraft) gets the whole-draft keys; other Claude cards keep xterm's own Command+A. - Command+A selects only a prompt visible on screen; a dialog in its place or a restarted Claude drops the selection. - The helper keeps the draft when it cannot save a copy and ignores unknown requests; the helper path may not contain shell syntax. - A localeless macOS launch gets LANG set to the system's UTF-8 locale (en_US.UTF-8 when the system lacks it), a name ssh can pass on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Pass the draft helper and the locale through Claude's --settings env as well: a background session (/bg, /resume, agent view) runs under Claude's supervisor and gets this launch's --settings, not its environment. - The helper answers a request in another card's folder after its own, so a background session shown by another card still works; it acts only on Claude's own prompt file (claude-prompt-*.md), never on a file a program Claude runs opens (git commit, crontab -e). - A cancel the helper cannot remove is not used up: that Ctrl+G opens the editor. - Agent isolation: a Claude card's own draft folder is writable and readable again; the draft root is private app data, hidden from other launches. - Command+V over a selected draft reads the clipboard when its turn comes, so keys pressed meanwhile stay after the paste. - Every launch resets the card's draft queue; leftovers of an earlier run are removed at startup; Command+A over a dialog drops an earlier selection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tor and locale - Inside agent isolation the shared draft root is hidden, so the helper no longer requires it: it looks in its own card's folder, then in whichever other card folders it can see. The seatbelt test now runs the real helper. - A cancel is used up only from the card's own folder: another card's timed-out action never swallows an ordinary Ctrl+G here. - A plugin that sets VISUAL or EDITOR in its own --settings env keeps Ctrl+G (no helper), and a locale a plugin sets is not replaced in the --settings env that background sessions get. - A request that cannot be claimed but is still there (also a dangling link) stops the helper instead of looping. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The helper is a POSIX shell script for macOS Claude cards; Windows CI has no /bin/sh to run it (spawnSync /bin/sh ENOENT). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every agent outside Manual was wrapped in sandbox-exec, whose writable list lacked /tmp/claude-<uid>. Claude Code keeps its Bash tool and external-editor (Ctrl+G) files there whatever TMPDIR says, so every shell call and the draft editor failed with EPERM. - The layer now applies only to delegated agents (subagents, plugin-started agents) and to a CLI's contained auto; an agent the person launched runs with their own environment. - Isolated Claude may write /tmp/claude-<uid>. - CanvasTTY no longer switches Claude's own sandbox on in Auto: it follows the person's Claude settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cope - Settings -> Agents -> Claude draft keys (macOS, on by default). Off, a Claude card no longer intercepts Command+A and its follow-up keys, new Claude sessions start without CanvasTTY's VISUAL helper, and draft actions do nothing. - Docs and changelogs: the isolation layer wraps subagents, plugin-started agents and a CLI's contained auto only; Claude's own sandbox follows the person's Claude settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A "Claude editor" group (macOS) next to the Codex editor, headed by the Claude draft keys switch (moved there from Agents). Defaults: submit Cmd+Enter, line break Enter, alternate line break Shift+Enter, select Cmd+A, copy Cmd+C, cut Cmd+X, delete Backspace (forward Delete too). - The submit and line-break keys act only while Claude's prompt box is on screen; dialogs and other screens keep Enter. Bindings apply at once in open cards; the switch off removes all Claude key handling. - claude* bindings form their own conflict context; submit, both line breaks and select are always bound, copy, cut and delete can be off. - Pasting over a selected draft follows the terminal Paste key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Expose Codex submit, newline, select, copy, cut and clear bindings in keyboard settings while preserving customized bindings and non-macOS behavior. Forward the actual launch keymap to renderer metadata on create, restart and restore so Command+X reaches the native editor. Cover settings migration, menu routing and launch lifecycle with regression tests.
The Claude scratch folder /tmp/claude-<uid> exists only on macOS and Linux; on Windows the check compared POSIX paths and failed the windows-pipe-host job. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The settings store's defaults follow the host, so on the Linux and Windows CI runners the migration tests got the old keys and failed. They now pass the macOS defaults explicitly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the follow-up. We ran #146 together with our open PRs (#147, #148, #133) and with real 1. Claude draft folder under isolation (still open from #145). It now only matters for delegated Claude cards, which are the ones still inside the layer, but it crosses the layer boundary there.
Suggested change (with tests; all pass on top of this PR): // environment(): create the folder before the agent starts
rmSync(this.directory(sessionId), { recursive: true, force: true });
mkdirSync(this.options.root, { recursive: true, mode: 0o700 });
mkdirSync(this.directory(sessionId), { mode: 0o700 });
// roundTrip(): instead of mkdirSync(directory, { recursive: true, mode: 0o700 })
if (!ownFolder(directory)) return false;
// ...and instead of lstatSync + readFileSync(draft)
const text = readDraft(draft);
function ownFolder(directory: string): boolean {
try {
const stat = lstatSync(directory, { throwIfNoEntry: false });
if (!stat) { mkdirSync(directory, { mode: 0o700 }); return ownFolder(directory); }
return stat.isDirectory() && !stat.isSymbolicLink() && (typeof process.getuid !== "function" || stat.uid === process.getuid());
} catch { return false; }
}
function readDraft(path: string): string {
let fd: number;
try { fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK); } catch { return ""; }
try {
const stat = fstatSync(fd);
if (!stat.isFile() || stat.nlink !== 1 || stat.size > 16 * 1024 * 1024) return "";
return readFileSync(fd, "utf8");
} catch { return ""; } finally { closeSync(fd); }
}In if (paths.pinnedFolders.length > 0) {
lines.push(`(deny file-write-unlink${paths.pinnedFolders.map((path) => ` (literal ${quote(path)})`).join("")})`);
}With real seatbelt the agent can still create and delete files inside the folder, but 2. // AgentIsolation.wrap, next to TMPDIR (L208)
const env = { ...launch.env, TMPDIR: `${temp}/`, TMP: temp, TEMP: temp, CLAUDE_CODE_TMPDIR: temp, GIT_TEMPLATE_DIR: gitTemplate, [ISOLATION_ENV]: ISOLATION_NOTE };and no 3. Scope of the policy change. If the EPERM was the main reason, would you consider keeping the layer for person-launched non-Manual agents? Without it:
If the narrower scope is intended, a launcher/doc note per profile would help. 4. Merge order with #147 / #148 / #133.
Small: the ru label "Редактор Claude" trips our #149 hint test (we will allow "Claude" there). |
|
Thanks for working on this. We need the keyboard fixes, and I understand that Claude's background sessions make this more complicated than fixing a temporary-directory path. Could we get the shortcuts working while preserving CanvasTTY's isolation guarantees? After reviewing the current head (
One important qualification on the supervisor issue: static inspection of Claude 2.1.296's Linux binary shows its socket under Possible ways forward:
References: background sessions and supervisor behavior, environment variables. Which approach do you think is practical? Can we fix the draft routing and filesystem issues and retain isolation in this PR, with full agent-view integration handled separately? If disabling agent view is too restrictive, do you see another way to keep the background worker inside the correct sandbox and bound to the correct CanvasTTY session? Happy to consider a smaller solution if we are missing an existing Claude mechanism. This follow-up is based on source review, documentation and static binary inspection; no live Claude/sandbox tests were run for it. |
This replaces #145. #145 was an early, unfinished state of this work; we found and fixed several bugs in it while using it. This PR is the complete, tested version.
A ready macOS arm64 build of exactly this branch (plus the Codex frontend below) to try it: https://github.com/mrcertis/CanvasTTY/releases/tag/1.8.1-mrcertis.1
Summary
macOS only, except the isolation change (all platforms) and two test skips on Windows.
sandbox-exec, whose writable list lacked/tmp/claude-<uid>. Claude Code keeps its Bash tool and external-editor files there whateverTMPDIRsays, so every shell call and Ctrl+G failed withEPERM. The layer now wraps only delegated agents (subagents, plugin-started agents) and a CLI's contained auto. Isolated Claude may write/tmp/claude-<uid>. CanvasTTY no longer turns Claude's own sandbox on in Auto: it follows the person's Claude settings. Base protection is unchanged and on by default.$VISUALhelper (src/agent-runtime/draft-editor.sh)./bg,/resume, agent view) through--settingsenv, and inside agent isolation.LANG, so Cyrillic no longer turns into mojibake inpbcopy/vi.CANVASTTY_CODEX_KEYBOARD.The Codex frontend
The Codex editor keys are read by the owned Codex frontend (the optional
codex-native-tuiresource from #103). Its side of this change is in mrcertis/codex-macos-tui@5fce773. A build without that frontend is unaffected: Codex cards keep the stock CLI and its keys.Test plan
windows-pipe-host: https://github.com/mrcertis/CanvasTTY/actions/runs/38469721818npm testlocally: all pass except "real CLIs start inside the layer", which fails the same way on cleanmainwith the locally installed opencode 1.15.13 (JSON Parse error: Unexpected identifier "FAKE")cargo test -p codex-tui --libforcanvastty,native_enter,select_all🤖 Generated with Claude Code