Skip to content

Inbox: Trust folder button for Claude Code launches - #2451

Open
backnotprop wants to merge 147 commits into
mainfrom
feat/inbox-launch-trust
Open

backnotprop wants to merge 147 commits into
mainfrom
feat/inbox-launch-trust

Conversation

@backnotprop

@backnotprop backnotprop commented Nov 1, 2026 •

Copy link
Copy Markdown
Owner

Stacked on #2402 (feat/inbox-launch-host).

What changes

Trust and start in New message is now the main path (approval .product/approved/inbox-launch-trust-compose/). On a folder Claude Code doesn't trust, New message's button reads Trust and start. One press trusts the folder and starts, with no confirm step.

  • A question circle beside the button opens the details: "Trust ?", naming the folder the trust lands on, and short fact lines, with no button inside. Press again, Escape or a press outside closes them.
  • The circle takes the warning color when the folder has hooks, MCP servers, helper commands, env settings, tools without asking, outside folders or unreadable files. "Whole repository, not just " alone keeps it quiet. A clean folder has no circle. Scratch folders work the same way.
  • A trusted folder keeps plain Send. Home, /, a folder above home and guests get no offer.
  • Cmd/Ctrl+Enter does what the button does, once. There are no new shortcuts.
  • On a phone, the button and circle sit in the bottom bar, the top bar keeps Cancel and the title, and the details open in the Inbox's bottom sheet. Main's Inbox: Send in a phone's bottom row, head folds over the keyboard #2466 puts a phone's Send in the bottom bar for every message, so a trusted folder's Send sits there too, with no circle.
  • The status line about starting a session is gone from the footer on every launch.

The thread's Trust folder button stays as the fallback for a launch that still stops on trust.

The press is the consent

  • GET /api/inbox/sessions carries launch.agents[i].untrusted, from the cached trusts(), so the 5 s refresh reads no folder's files. ?trust_agent=&trust_project= adds facts and warns for the folder To names. Facts and the run line's warning come from one reading (claudeTrustReading).
  • A Send carries to.launch.trust: { folder, facts }. If the folder changed since that reading, the answer is 409 trust_warning_changed: nothing is trusted, sent or made. The window reads again, opens the details on what changed, and ignores presses for 600 ms.
  • Order: validate, trust, write the thread, start. A taken thread name or a bad upload refuses before the trust is saved. 409 trust_failed sends nothing, keeps the words and removes a scratch folder made for that Send.

The gate

Compose's Send can now write the person's Claude config, so to.launch.trust belongs to the owner's window alone.

Caller Answer
The owner's window, or the owner's login over the tailnet Goes through
An agent's bare call, a bearer token, no serverSession 403 window_only
An MCP client send_message has no such field
The device door Refused, and gets no offer
A guest on the allow list 403 owner_only, and gets no offer
Another site's page 403 cross_origin
A tab from an older run 409, stale tab

A trust key that holds nothing still counts as a press, so null can't pass as a plain launch. Behavior change: POST /api/inbox/launch/:id/trust now has the same window check. Before, a bare local call counted as the owner. A local program that forges Origin and reads serverSession stays "Accepted, not defended" in inbox-launch.md.

The thread's Trust folder button

When Claude Code refuses a start or a restart because the folder isn't trusted, the run line gets a Trust folder button. One click saves the trust, then starts the launch again, so the kept message goes out with no second click.

  • It works over the tailnet and on a phone, where Open in Terminal is hidden. It's for the machine's owner only: a guest on the allow list gets no button, and the route answers 403 owner_only.
  • Before the click, one plain line says what Claude's own trust prompt warns on, such as "This folder has 2 hooks, 1 MCP server and env settings that will run once you trust it." A folder with none of these shows no line.
  • The click sends the line it showed. A folder that changed since saves nothing and answers the new line; the next click goes through.
  • A failed write says why under the line, and the message stays kept.
  • The home folder, the file-system root and a folder above home get no button. With no button the line says how to trust a folder by hand, and that the home folder can't be trusted: Claude never saves trust there, so accepting its prompt does nothing.
  • Every launch's settings file sets enableAllProjectMcpServers: true: Claude's "New MCP server found" prompt would otherwise hold the hidden session, and the folder's hooks behind it. disabledMcpjsonServers still wins. Found by a real claude check of ffc4d22.

The write

It sets projects["<key>"].hasTrustDialogAccepted = true in the file Claude picks: a legacy .config.json when it applies, else $CLAUDE_CONFIG_DIR/.claude.json, else ~/.claude.json, with the custom OAuth name when that is set. Only that value changes: every other byte stays, and so does the file's mode. A symlinked config is written at its target. The write is flushed to disk before the rename, as Claude's own save is.

  • The key: the physical NFC path of the git root (a linked worktree's main checkout), else the folder. A scratch launch trusts the scratch root.
  • The lock: Claude's own, read from the 2.1.316 binary. It is a directory made with mkdir at <config path>.lock and released with rmdir. It counts as dead once its mtime is 10 s old. The Inbox holds it only for a synchronous read, edit and rename.
  • Refusals: a file that isn't JSON, is empty, is over 32 MiB, or whose lock stays held for 12 s is never replaced. An empty file is Claude's to repair.

claudeTrustsFolder now reads the same key, which fixes the two mismatches: a linked worktree of a trusted repository reads as trusted, and paths compare physically.

Open in Terminal

Owner's decision, in its own commit: out of the ⋯ menu. It shows on the thread's status line only when a session is waiting on Terminal or can't start. Copy attach command stays in ⋯.

  • The thread decides it once (openTerminal), for the line and for Approve in Terminal on the permission card, with the rule the ⋯ item had: not on a phone or over the tailnet, and only with a session or a refused folder.
  • Fix: the run line drew the button in a tailnet window, where the route refuses it. It no longer does, and that window gets the phone's "Answer it in Terminal on your Mac:" line above the command.
  • New on the line: a failed line with a session (never connected, a restart that failed) has the button. ⋯ used to be its only way there. The rule looks at the session, not at attach_command, so a host that offers no attach command while something holds the session still has it.
  • The press re-reads the line, as ⋯'s did, so a host's hand-off shows at once.
  • The route and each host's terminal() are unchanged, so a host's own guard on the command stands. That's the guard against a second writer on feat/inbox-launch-pi.

Fixes from the security review

  • New message on a slow read: an answer with a newer read behind it was dropped and the 5 s refresh asked again, so a sessions read taking 5 s or more left compose empty and Send doing nothing. That also reached people with launching off. An answer is now dropped only when it was asked for another folder.
  • Open route: a test now posts to it over the tailnet, as owner, guest and no owner, and gets 403 owner_only.
  • Thread name: it's checked again with Claude's lock held, so a name taken during the lock wait saves no trust. accept takes an allowed callback for this.
  • One offer: Send saves the offer the press was compared with, not a second one.
  • FIFOs: claudeTrustKey skips a .git that isn't a regular file or folder, and reads its pointer files through readAgentFile, so a FIFO can't block the Inbox.

Copy and docs

  • The untrusted cause is now " isn't a trusted folder yet." The old text told a phone to open Terminal.
  • The scratch row's "trust it once" hint now shows only where no press can trust the root (a guest, a root at home).
  • inbox-launch.md covers Trust and start, the offer, the press, the gate and the seam (trustOffer(target): { folder, read(), accept() }), and no longer says the Inbox never edits ~/.claude.json.
  • server-and-storage.md has the new route and fields.

Test sandbox

Under PLANNOTATOR_TEST_SANDBOX, the server's launch home is now <dataDir>/agents-home, so a test that clicks through the real server can't reach the real ~/.claude.json. refuseRealClaudeConfig refuses a home or its Claude folder as a test's config dir. startInboxServer gains a launchHosts test seam, and fakeLaunchHost moved to tests/helpers/.

Tests

  • Compiled build, Playwright, Chromium, fake claude: the launch specs run in CI's Inbox window e2e on this PR, on Linux. Locally, the five (inbox-launch-claude, -permission, -scratch, -trust, -trust-compose) with the Pi spec, 54 passed on a trial merge of this branch and Inbox launch: Pi host #2412. The compose spec covers desktop and phones 390 and 320 wide.
  • Server, on a real server with a fake host: inbox-launch-trust-send.test.ts, 32 tests for the gate, the order, 409 and trust_failed.
  • DOM: composeTrust.test.tsx (23) and openInTerminal.test.tsx (9), both rendering the real components.
  • CI on this head (ci:full): unit-1 to unit-3, dom-1 to dom-4, heavy-agents, heavy-mod and Checks all pass.
  • Mutation checks, each reverted: the gate, the owner check, the reading comparison, the held-key and 600 ms guards, the Open in Terminal rule, the tailnet line on the Open route, the old useComposeSessions check, the name re-check under the lock, the reused offer, and both FIFO guards each failed tests when removed or changed.
  • Not run: heavy-* lanes, the full bun test, WebKit, and real CLIs. The real-CLI check is the owner's.

backnotprop and others added 30 commits October 30, 2026 09:56
PR 1 of the launch plan. Compose's To offers "New Claude Code session" under
the running sessions; Send writes the thread first, then starts the agent with
`claude --bg` and a per-launch settings file (login-shell CLAUDE_* env, 0600).
The mod's existing bridge poll delivers the first turn. A launched thread
shows one run line (starting, working, waiting on a permission prompt,
stopped, failed, missing), Stop / Open in Terminal / Delete in the thread
menu, and a reply to a stopped session respawns it. Full permissions is a
Settings → Agents switch.

Config-only flag `inboxLaunch.enabled`, default off: off, the launch routes
answer 404 and the window is unchanged.

Tests: tests/e2e/inbox-launch-claude.spec.ts against the real endpoint with a
fake `claude` (tests/helpers/fake-claude); unit tests for the run-line mapper,
the env filter and the CLI parsers. scripts/launch-smoke/ is the manual
real-CLI smoke.
- A permission-prompt line goes once the session is live and idle (answered
  in Terminal); the host is re-read while it last said waiting.
- The login shell is read per start only, from a minimal seed env; every
  other claude call runs with the Inbox's own env, scrubbed of PLANNOTATOR_*,
  CLAUDE* and Claude's per-session names (CLAUDE_PID, CLAUDE_CODE_SESSION_*,
  CLAUDE_CODE_MESSAGING_*, CLAUDE_CODE_CHILD_SESSION, CLAUDE_CODE_EXECPATH,
  CLAUDE_CODE_ENTRYPOINT, CLAUDECODE).
- inboxLaunch.extraSettings is gone; one validated inboxLaunch.model.
- A reply respawns once; a failed or unconnected respawn reads failed with
  Try again, never retried by itself.
- Config read once per list (cached by config.json stat); launched roots
  indexed in the store.
- No claude call is killed with its process group: the shared supervisor is
  never in a group we kill.
- Delete or Try again during a start leaves no orphan session.
- Starting holds until the session polls busy or answers (no blink).
- Phone: no Delete; re-address offered only when the message can move.
- Tests: wait-fors instead of sleeps, no wording snapshot, e2e for the
  prompt answered in Terminal, Delete during --bg, re-address in 15, phone
  01/08; the smoke checks the supervisor's group and one login read.
- N1: every claude call keeps the person's CLAUDE_CONFIG_DIR and other
  CLAUDE_* settings; only Plannotator's names and Claude's per-session ones
  are dropped.
- N2: the login shell starts from the Inbox's env through the same filter,
  so SSH_AUTH_SOCK, launchctl proxies and LC_* survive.
- N3: Stop while "Starting again" reads "You stopped", never a failure, and
  the same reply does not respawn it again.
- N4: the shell's own skip-permissions export never rides the --bg env; it
  is set only when the full-permissions switch is on.
- N5: availability says what it checked (the Inbox's PATH, the last start's
  login PATH, Claude's install folders).
- Try again on a start that never connected respawns it (or starts anew
  when no session was made) instead of answering 422.
- Test comment and smoke notes corrected.
A per-launch PermissionRequest hook (plannotator inbox permission-hook) asks
the person in the launch thread under the perm:<session> identity, long-polls
/api/inbox/launch/<id>/permission, and prints allow once, allow for this
session, or deny with a reason. ExitPlanMode passes straight through. A 60 s
lease withdraws the question when the prompt is answered in Terminal or no
longer waited on. The run line gains waiting_answer. Behind inboxLaunch.enabled.

Co-Authored-By: Claude <noreply@anthropic.com>
… smoke

Co-Authored-By: Claude <noreply@anthropic.com>
M1: a command's list lines can never become choices. The command and
folder now sit above the :::question block, which holds only the prompt
and three "- [ ]" choices; lines starting ":::" are escaped. A pick is
read by exact label. The route checks the stored question has exactly
this prompt's labels, and withdraws it otherwise (fail closed).

S1: the Inbox sends the hook only { choice: once|session|deny, reason }.
The hook builds the decision from its own event, so whatever answers on
the port cannot change tool_input or grant more than Claude suggested.

S2: picks/reply on a perm: question need the page's own Origin and this
run's serverSession (403 window_only otherwise). The perm: author prefix
is reserved to the permission route in the store. Docs say what this
does and does not protect against.

S3: "Answered in Terminal" only when no hook call is held.

S4: each hook call is cut off at 35 s; inbox.json is read again on each
retry; 15 refusals of one token end the wait with a stderr line.

Low: a command over 8,000 chars is never asked (Claude's dialog
decides); permission styling only in a launched thread with launch on;
turning launch off withdraws open questions; reuse the Inbox's
selfCommand; AskUserQuestion passes through like ExitPlanMode; refusals
log to stderr; dead code and the unused lease field are removed.

Co-Authored-By: Claude <noreply@anthropic.com>
N1: a path (Write, Edit, Read, NotebookEdit), a link (WebFetch) or a
query (WebSearch) is one line of inline code; one holding a line break,
a control or an invisible character is never asked (withdrawn, Claude's
own dialog decides). Every detail counts toward the 8,000 cap. The
route now checks the stored body, prompt and labels against the
question it would write now, so a reshaped card fails closed.

N2: the session label names every rule or folder the suggestion grants
("Allow `git add` and `git commit` for this session"); a rule it cannot
show exactly, or a label over 200 characters, offers no session choice.

N3.2: the store refuses a Re-ask on a perm: message (422), and the
window no longer offers one.

Phone: a request the device door hands in (its token checked) may
answer a permission question; a plain local call without the page's
Origin is still refused.

Low: bidi and invisible characters are shown as <U+202E> markers in
every detail and label, never hidden; asked() returns { prompt, detail }
and inboxPermissionQuestion decides askable; the stale "(after one more
host read)" is gone; the docs state the accepted risks (a local program
forging headers, browser automation clicking the window).

Co-Authored-By: Claude <noreply@anthropic.com>
…missions

# Conflicts:
#	packages/inbox/api.ts
Builds approval "inbox-permission-prompt" (card frame and width, the dock's
header) into the launch thread:

- PermissionCard: header "Claude Code is waiting for your permission · N min"
  with Stop, in place of the separate run line; the action title and the exact
  request (command with purpose and folder, Write content as numbered lines,
  Edit as removed/added lines, WebFetch link with host and "Looking for");
  Allow once (accent), Allow <rule> for this session, Deny, Deny with a
  reason; "Or allow this and switch the session to Auto mode · Full
  permissions". Answered, it shrinks to one framed line; the answer reply and
  the withdrawal follow-up are hidden.
- Mode switches are real: two new exact-label choices, enum "auto" | "full";
  the hook answers allow with setMode auto / bypassPermissions built from its
  own event. Full permissions only for a launch with full permissions
  (inboxLaunchFullPermissions, the one seam for the open question) and only
  when the hook's own env has PLANNOTATOR_LAUNCH_FULL_PERMISSIONS=1;
  otherwise drawn off with its reason. Confirms the first time per session.
- Allow once takes focus when the card is newest and waiting, and the footer
  is scrolled into view, so Enter answers it. No new shortcuts.
- Body additions (Write content, Edit diff, Bash description, WebFetch
  purpose) go through the same fencing and escaping; the stored body is still
  compared with the rebuilt one (fail closed).
- The questionChrome seam in packages/ui is removed again (packages/ui equals
  main).

Tests: escaping of a Write holding fences and "- [ ]" lines; mode-switch
choice, route and hook tests; e2e of the approved card through the real hook
(Write by Allow once with Enter, Bash by the session rule, WebFetch denied with
a reason, Edit by Auto mode with setMode auto, Full permissions off and
confirmed).

Co-Authored-By: Claude <noreply@anthropic.com>
- The session button wraps, so every rule it grants shows on desktop and phone.
- A WebFetch link is shown exactly as sent (scheme, host, path, query), wrapped,
  with "Host: ..." on its own line when the text does not start with the real host.
- Allow once takes focus only from the page body, without a focus ring; the
  answers ignore activation for 600 ms after the card appears or takes focus,
  and a repeated keydown never clicks.
- Full permissions has one source, the launch record, for the start flags, the
  hook's env and the card.
- Picks on a permission question are unsent-only in the store; the first Full
  permissions pick of a session needs confirm_full on the server too.
- The request in full: Write says create or overwrite, Edit shows replace_all,
  MultiEdit and NotebookEdit show their edits, Bash shows "Runs outside the
  sandbox", and undrawn input keys show as JSON.
- Auto and Full records say what was asked, never that it switched.
- Session rule labels keep their backticks (code spans parsed properly).
- More invisible ranges shown (tag block, soft hyphen, Hangul fillers,
  variation selectors); doc comments fixed; disabled Full permissions note in
  flow, never over Deny.

Co-Authored-By: Claude <noreply@anthropic.com>
…sync file check

- A reply carrying its own answer for an answered permission question posts its
  words as text and leaves the answer record alone (question_already_sent when
  nothing else is left to send).
- The Write overwrite check is an async lstat given 300 ms, so an unreachable
  network path never freezes the Inbox; a timeout or error says "Write a file".
- A file tool's path that is a symbolic link shows where it points
  ("Links to ..."), kept the same when the question is checked again.

Co-Authored-By: Claude <noreply@anthropic.com>
…us keep one line

The answered record's path takes only the room left and truncates first; the
waiting header reads "waiting · N min" on a phone. The e2e checks both at 390 wide.

Co-Authored-By: Claude <noreply@anthropic.com>
…uto mode

- Never set, Settings -> Agents' "Start new sessions with full permissions"
  follows the login shell: CLAUDE_CODE_EVEN_MORE_DANGEROUSLY_SKIP_PERMISSIONS
  set to a true value makes the launch full (launch.full_permissions, still the
  one source, fixed when the launch is made; the first start reuses that read).
  Once the person sets the switch, their choice wins either way. Settings shows
  it on with "Follows your shell's setting until you change it."
- Every launch starts with --allow-dangerously-skip-permissions and
  --permission-mode bypassPermissions (full) or auto. Auto mode refused: started
  again with no --permission-mode, and why goes to the start log.
- The card always offers Full permissions; the greyed-out state is gone.
- Fake CLI: asks nothing in bypass mode, refuses auto on refuseAuto, and its
  login shell takes extra exports. E2E for the shell default and the fallback.

Co-Authored-By: Claude <noreply@anthropic.com>
… no hook in a full launch

From the security review of c445a71:
- M1/S1: at launch, read the person's own bypass consent (read-only) into
  launch.bypass_available. The allow flag, a bypass start and the card's
  Full permissions need it; older launches read as without it. The Inbox
  never writes consent. The fake CLI models Claude's consent rule.
- S2: GET /api/inbox/launch/settings refuses another site's page; concurrent
  reads share one login-shell read.
- S3: the auto fallback pins permissions.defaultMode "default" in that
  launch's settings file; only commander's refusal of the mode flag counts.
- S4: a full launch has no PermissionRequest hook (Claude's skip export
  waives bypass's safety checks only without one); the Inbox tool stays allowed.
- The order: the Settings switch, the person's own defaultMode (no
  --permission-mode, never forced to auto), the shell's export, then auto.
- Nits: stale comments, the login env kept at most a minute; a card already
  there when the thread opens leaves Reply its focus.

Co-Authored-By: Claude <noreply@anthropic.com>
… Claude reads it; Send off the shell

From the re-check of 499db45:
- M1: the Inbox PermissionRequest hook is in every launch, full included.
  Plannotator's plugin has its own PermissionRequest hook, and Claude then
  ignores the skip export, so bypass's safety checks still ask; they now
  reach a card. The export is no longer set anywhere. The fake CLI counts the
  enabled plugin's hook; e2e proves a full launch's safety check reaches the
  card. Docs no longer say a misjudged bypass fails to start.
- S1: consent only from managed or user settings.json and ~/.claude.json;
  disableBypassPermissionsMode from managed, user settings.json and
  settings.local.json, and the launch folder's settings.
- S2: every auto launch pins permissions.defaultMode "default".
- S3: Send uses a shell read under a minute old and waits only to finish one
  under way, when the switch is unset and none is fresh, or when none ever
  finished; read once at Inbox start and ahead when the composer picks Start
  Claude Code; the start reads its own env after the response.
- S4: a person's own mode applies whenever the launch is not full.
- Nits: a Full pick a session cannot honour is Allow once; a full launch's
  card offers no Full; Claude files read once per settings request.

Co-Authored-By: Claude <noreply@anthropic.com>
…read as Claude reads it

From the review of 3e6006e:
- S1: prepare decides from the last finished login-shell read, however old
  (each start reads the shell again); it waits only for a read under way or
  when none ever finished.
- S2: disableBypassPermissionsMode counts only as "disable", from managed,
  the user's settings.json and the launch folder's settings (never
  ~/.claude/settings.local.json). Settings says plainly when bypass is
  turned off in Claude Code's settings (bypass_off), never the consent notice.
- Nits: a full launch's card offers no mode switch; the Settings copy allows
  that a safety check still comes to the card; the stale env header and the
  docs on the ahead read are fixed.

Co-Authored-By: Claude <noreply@anthropic.com>
New scratch folder in compose's folder menu makes <root>/<thread-name slug>
(-2, -3 when taken, never through a link) and starts Claude Code there.
The root is inboxLaunch.scratchDir (default <dataDir>/scratch), set in
Settings > Agents. While Claude Code does not trust the root the row reads
'trust it once', and the failed start's Open in Terminal runs claude in the
root. Delete purges and removes a scratch folder, only while it is still the
folder the Inbox made; never a project folder.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude keys workspace trust on a git repository's root, and a parent's trust
never covers a nested repository (real-CLI smoke, code.claude.com docs). The
Inbox makes scratch folders as plain folders, so the root's trust covers
them. A scratch folder the agent made a repository needs its own trust: a
reply's restart refused as untrusted now offers Open in Terminal, which runs
claude in that folder. claude purge runs with --yes, as its confirmation
would wait on a terminal no one sees. The fake CLI models both.

Co-Authored-By: Claude <noreply@anthropic.com>
claudeTrustsFolder and where a launch's trust is keyed move to inbox-launch-claude.ts; inbox-agents.ts only exposes the config path. Scratch-folder creation and removal stay agent-neutral.
…aunch's safety check is Deny or Terminal

Claude Code 2.1.314 ignores a PermissionRequest hook's allow for its own
safety checks (person-only) and keeps its Terminal dialog; a deny counts.
The hook input marks nothing, so the launch's mode decides.

- A full launch's card offers Deny and Deny with a reason, the host's note
  ("Claude Code only accepts this approval in Terminal."), Open in Terminal
  (computer) and Copy attach command (everywhere).
- Every allow is checked: the first host read 3 s after the answer decides.
  Still waiting on a permission prompt: a follow-up says Claude Code didn't
  take it, and the record shows that with the Terminal actions; until then
  "Sent, checking that it went through". Never "Allowed" before it took.
- Host reads from before an answer settled never show "waiting on a
  permission prompt" for the prompt just answered.
- The thread read carries attach_command, so the resting menu has Copy
  attach command (phones).
- Claude-only terms, note, follow-up text and the attach command live in
  inbox-launch-claude.ts; core and the card take generic allowHere/note.
- Nits: Try again of a start that never ran is a first start (no "again");
  no "· N min" under a minute.
- The fake CLI models person-only safety checks (allow ignored, own dialog,
  auto-block after safetyBlockMs) and lists waitingFor while it waits.

Co-Authored-By: Claude <noreply@anthropic.com>
…nto feat/inbox-launch-host

# Conflicts:
#	docs/agent-guides/inbox-launch.md
#	docs/agent-guides/server-and-storage.md
#	packages/server/inbox-launch-claude.ts
…safety checks to Claude's own dialog

Simplify after review: no allow-verification state machine.

- Removed the allow checks (settle/max timers, "Sent, checking", the
  "didn't take" follow-up, run.approval_check). The record states the
  person's own answer ("You allowed this once", "You denied this", "You
  chose to approve this in Terminal"); the run line says what Claude does.
  Host reads made before an answer settled still count for nothing, and one
  read is due once it settled.
- M1: the hook input's permission_mode decides bypass mode
  (claudePermissionTerms in inbox-launch-claude.ts); the launch record only
  when the input has none. A full launch whose session says default keeps
  Allow once.
- M2: a bypass card offers Deny, Deny with a reason and Approve in Terminal.
  Approve in Terminal answers `terminal`: the hook prints nothing, so Claude
  shows its own dialog (2.1.314 awaits the hooks first); on a computer the
  card opens Terminal attached, the record says "Approve it in Claude Code
  within 2 minutes", and on a phone Copy attach command until the run line
  shows it. One set of Terminal actions.
- The bypass card's header says "is waiting on a safety check"; its question,
  withdrawn with the hook gone, says "No longer waiting." (never "Answered in
  Terminal.").
- The fake shows a safety check's dialog only after the hook ends, with its
  own 2-minute deadline from then.

Co-Authored-By: Claude <noreply@anthropic.com>
…nto feat/inbox-launch-host

# Conflicts:
#	docs/agent-guides/inbox-launch.md
#	docs/agent-guides/server-and-storage.md
#	packages/server/inbox-launch.ts
…lete, purge smoke)

M1: a deleted launch's Delete does nothing; a path any launch was given is never given again; Delete checks the folder's inode. S1-S7: re-check before removal, cached trust read, no orphan project on a refused Send, no scratch root inside a git repository, trust through the host, the trust-once row names the root with a hint, a kept folder is said in the toast. Purge skipped below a repository's root (the real purge on 2.1.314 drops the repository's entry there).
…nto feat/inbox-launch-scratch

# Conflicts:
#	docs/agent-guides/inbox-launch.md
#	docs/agent-guides/server-and-storage.md
#	packages/server/inbox-launch-claude.ts
#	packages/server/inbox-launch.ts
# Conflicts:
#	docs/agent-guides/inbox-launch.md
# Conflicts:
#	packages/inbox/components/Thread.tsx
# Conflicts:
#	apps/hook/server/inbox-command.ts
…en's first fresh read, never a stale copy

App records the ids of the first thread read numbered after the open and
passes them to ThreadPane (openedWith); until that read lands no card takes
focus. Adds a DOM test for the focus rule (permissionFocus.test.tsx) that also
covers the insecure-context answer.
# Conflicts:
#	docs/agent-guides/inbox-launch.md
# Conflicts:
#	packages/inbox/components/Thread.tsx
backnotprop added a commit that referenced this pull request Nov 3, 2026
backnotprop added a commit that referenced this pull request Nov 3, 2026
The agent is blocked until the person answers, so a new permission question
ends the thread's snooze at once (owner answer q-21cc8c10 A). A replayed hook
call does not.
…older

In the home folder: "Your home folder can't be trusted. Start in a folder
inside it." Anywhere else: "To trust it, run <agent> here in a terminal on
the Inbox's computer." The run carries home_folder when the launch folder is
the Inbox's home (owner answer q-aa43fc24 A).
# Conflicts:
#	packages/server/inbox-launch-permission.ts
… subpaths; ui pins it

Core gains ./inbox-run-line, ./inbox-permission and ./inbox-scratch across the
launch-agents stack; published core 0.25.17 has none of them, so smoke:package
failed. 0.25.18 is published from main after the stack merges.
runOf compared resolve() paths, which misses a symlinked home or another case
on macOS while the Claude host decides home by placeKey. Use samePlace; host
tests for a linked home and a trailing slash (review P2-B1).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:full Run the heavy CI tier on this PR (added by the merge script)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants