Moshpit

Guide

Run Claude Code from your iPhone

This is the walkthrough the app exists for. Four steps: get a host reachable, put herdr on it, start Claude Code, and answer its permission prompts from the lock screen. Everything here is a real string from the shipping app or a real command Moshpit runs.

Reach the host

Add the SSH details, check the host fingerprint once, pick herdr as the multiplexer for that connection.

Start the agent

Type claude in a herdr pane, or let Moshpit cut a fresh git worktree and start Claude Code inside it.

Put the phone down

When Claude Code asks permission, the lock screen says so and carries the buttons that answer it.

What you need before you start. A server you can already reach over SSH, an iPhone running iOS 18 or later, and Claude Code installed on the server. Moshpit does not install Claude Code and does not talk to Anthropic — it drives a shell, and Claude Code is just a program running in it.

herdr is optional. It is what makes agent status a fact instead of a guess, so this guide uses it. The tmux route works too and is covered in Using tmux and Answering agents.

Step 1

Get the host reachable

Four fields and one fingerprint check. If you can already ssh into the box from a laptop, this part takes a minute.

Add the connection

On Home, tap in the header. The form is grouped:

  • CONNECTIONName, Host, Port (defaults to 22), Username. Save lights up once Name and Host are filled.
  • AUTHENTICATIONPassword or SSH Key. A key can be one you made in Settings → SSH Keys, or a PEM pasted straight into the form.
  • ADVANCEDMultiplexer, set to herdr. Leave the custom path field empty unless the binary is somewhere unusual; a custom path is trusted verbatim and skips the capability probe.

The multiplexer is chosen per connection, not globally. Your hosts do not all have the same tools installed, and a global default would only produce "why doesn't this one connect".

Check the fingerprint once

The first time you connect, the handshake pauses and Moshpit shows the host key it was offered:

New Host

First connection to your-host:22.

Key fingerprint:
SHA256:

Verify it matches the server (e.g. `ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub`).

          [ Cancel ]   [ Trust ]

Moshpit cannot verify the fingerprint for you — it shows you the command to compare against. If a stored key ever changes, the dialog turns into ⚠️ Host Key Changed with both fingerprints printed, Disconnect as the default button and Trust New Key marked destructive.

Limitation worth knowing now. There is no screen that lists or forgets trusted host keys. Deleting a connection does not forget its fingerprint, and trust is stored per device and excluded from backups — a new phone re-prompts for every host. After a legitimate re-key, accepting the change in the ⚠️ dialog is the only in-app path.

Also: passwords and PEMs typed into the connection form are saved without a Face ID gate. Only keys created in Add Key with Require Face ID prompt on every read. Passphrase-encrypted private keys are not supported at all.

Step 2

Put herdr on the host

herdr is a single Rust binary written for CLI coding agents. It reports agent_status as a protocol field on every pane, which is why Moshpit needs nothing installed on your host to know that Claude Code is waiting on you.

Install it

# macOS / Linuxbrew
brew install herdr

# everything else
curl -fsSL https://herdr.dev/install.sh | sh

There is no apt-get line to give you: herdr is in no Linux distribution repository, and its own packaging only covers Windows. Moshpit refuses to synthesize sudo apt-get install -y herdr for that reason — a command that ends in "unable to locate package" reads as an app bug.

The installer drops the binary in ~/.local/bin and deliberately touches no rc file. Moshpit adds that directory to the PATH it uses when probing and when launching herdr, so you do not need to change anything on the host.

If it is missing, the app says so

Connect with herdr selected on a host that does not have it and you get a dismissible banner, not a failure:

  • Banner: "herdr not found on this host — plain shell session." with an Install herdr action.
  • The Install Assist sheet opens with the right command already filled in and the line "Moshpit never installs anything silently. Run the command below in your shell — sudo and its output stay fully visible."
  • Run in terminal pastes it into the live shell so sudo prompts and output stay visible. Copy command and Re-check sit next to it.
  • The session still works. You get a plain shell, and it is never quietly switched to tmux — tmux and herdr hold separate, unrelated sessions, so attaching the other one would show you someone else's work and call it yours.

Which herdr version this was built against. The integration was designed against herdr main / v0.8.0, protocol 19, and verified on real hardware against 0.7.3, protocol 16 — which is what brew install herdr installed at the time. The snapshot decoder is written so a missing field degrades that one detail instead of failing the whole read.

The visible cost on 0.7.3: it reports no pane command, so the third segment of the terminal breadcrumb falls back to pane N instead of naming what is running. The crumb is kept anyway, because it is the only way into the Select Pane sheet.

Use SSH, not Mosh, for this guide. Mosh transmits rendered screen diffs, which destroy line framing, so it cannot carry herdr's frame protocol (the same reason it cannot carry tmux -CC). Over Mosh, herdr runs its own full-screen TUI and Moshpit is only the renderer — on a phone that costs roughly a third of the screen width to herdr's sidebar. The control-plane sheets still work through a separate SSH sidecar.

Step 3

Start Claude Code

Two ways in. Run it in a pane you already have, or let Moshpit cut an isolated git worktree and start it there. The second is the one worth reaching for from a phone.

In a pane you already have

nothing new to learn

  • Type claude in a herdr pane — that is the whole step
  • herdr picks it up by watching its own pane's screen content
  • Nothing to configure, and nothing new to remember

New Agent Task

a worktree, a workspace, an agent

  • One sheet, reached from the AGENTS header on Home
  • Cuts a fresh git worktree on the host and starts Claude Code in it
  • Your working tree never moves
  • herdr only — tmux has no equivalent

Either way, herdr infers the agent from screen content. If you want it reported rather than inferred, run herdr integration install claude on the host once — Claude Code then announces its own state. It is more authoritative and it is not required; everything in this guide works without it.

The sheet, field by field

Group TASK, footer: "Creates a git worktree on the host, then starts the agent inside it. Your working tree is untouched."

  • Repo — a menu. Moshpit finds candidates two ways at once: the cwd of every open pane resolved with git rev-parse --show-toplevel, and a scan of $HOME by modification time. While it looks, the menu reads "Looking for repositories…"; if nothing turns up, "None found — no panes in repos, and nothing under ~". The last item is always Other…, which reveals a Repository path field.
  • Branch — checked on the phone before any round trip. Empty names, spaces, a leading - or /, a trailing /, .., a trailing .lock and the characters ~ ^ : ? * [ \ are all refused with a plain message, for example "No spaces in a branch name". Invalid input never reaches the host.
  • Agent — read from herdr server agent-manifests --json. The default prefers claude, then codex, then whatever is first. (Alphabetical order made the default agy on a real host, so it is no longer alphabetical.)

Group FIRST MESSAGE, footer: "Optional. Sent to the agent once it's running — leave blank to type it yourself." The Start button becomes Starting… while it runs, because a large checkout takes tens of seconds and a button that never changes reads as broken.

New Agent Task sheet: repo payments-api, branch fix-webhook-retry, agent claude, and a first message

What Start actually runs

Three commands over SSH, in order. Nothing is hidden, and every label you typed is single-quoted before it goes anywhere near a shell.

herdr worktree create --cwd '~/code/payments-api' --branch 'fix-webhook-retry' \
     --label 'fix-webhook-retry' --focus --json
→ {"type":"worktree_created",
    "workspace":{"workspace_id":"w4"},
    "root_pane":{"pane_id":"w4:p1",
                 "cwd":"~/.herdr/worktrees/payments-api/fix-webhook-retry"}}

herdr pane run 'w4:p1' 'claude'          <- types the command and presses return

herdr agent send 'w4:p1' '<first message>'   <- 2s later, only if you typed one

A new git worktree, a new workspace, a pane already sitting in the checkout, focused. Step two is deliberately pane run rather than agent start: agent start --workspace does not inherit the worktree's directory, so its pane lands in the wrong place. pane run types exactly what you would have typed, and you can see it happen.

What you will see first

On the hardware verification run, the workspace appeared with cwd ~/.herdr/worktrees/…/fix-scroll-jump, git worktree list confirmed the branch was really created, and Claude Code started and stopped at its trust-directory prompt. herdr marked the pane agent: claude / status: blocked, and the Agents section on Home immediately read AGENTS 1 · NEEDS YOU. That first prompt is the one you answer in step 4.

Cleaning up afterwards

Long-press the workspace row on Home → Remove Worktree. The item only appears on workspaces that really are a linked worktree. The first command never carries --force:

  • Remove the worktree for "…"?"Deletes the branch checkout under ~/.herdr/worktrees. payments-api itself is untouched."
  • If herdr refuses because the worktree is dirty, and only then, a second dialog: "…" has uncommitted changes"Those changes exist nowhere else. Removing the worktree throws them away." Buttons Delete anyway / Keep it. Only that explicit tap adds --force.

Limits on this step, stated plainly.

Worktrees land in ~/.herdr/worktrees/<repo>/<branch>, not beside the repo, and there is no path option in this version — it is not a thing worth typing on a phone. Large repositories take tens of seconds to check out. Behaviour of git worktree add against a dirty repository is expected to be fine, but has not been verified.

Agent flags are not chosen for you. Whatever the manifest's default claude command is, that is what runs. Options like --dangerously-skip-permissions have to be typed by you, in the pane.

herdr only sees agents in its own panes. A claude you started in Terminal.app on the same machine does not appear — herdr is a runtime, not a process scanner. This was hit for real: five idle herdr panes, one busy claude in a separate terminal, and the Agents section correctly said "Nothing running — start a task to isolate one" while the user was certain something was running.

Not verified on device: the long-press context menu itself. Automation cannot drive a SwiftUI long-press, so the menu → dialog → dialog sequence is guaranteed by compilation and unit tests, without a device recording.

Step 4

Answer the permission prompt from the lock screen

Claude Code stops and asks whether it may run a command. Amber is the only colour on this site that means "an agent needs a human", and this is where it shows up.

iPhone lock screen with an Moshpit Live Activity: Claude Code with Allow and Deny buttons, and codex working below it

The row that is blocking State dot, the command in mono, the location as host · workspace · tab, and a live counting-up timer. The most-urgent agent sorts to the top and is the one that gets buttons.

The ones that are not Everything below the headline collapses to a status line. That is deliberate: iOS clips a tall Live Activity, and what fell off the bottom was exactly the second agent's Allow and Deny.

Where the buttons run In Moshpit's own process, not in the widget. iOS launches the app into the background to handle the tap. The screen stays locked.

What each button types

They are raw keystrokes into Claude Code's pane. Nothing else, and nothing clever.

ButtonBytes into the paneShown when
Allow 0x0d — a bare Enter, accepting the highlighted optionneeds you
Deny 0x1b — Esc, cancel needs you
yes / continue that text, then Enter needs you
Stop 0x03 — Ctrl-C working
Reply your typed text, then Enter notification action

The mapping is tuned for Claude Code's permission prompt, where the affirmative option is default-highlighted and Esc cancels — which also fits most TUI yes/no menus. They are blind keystrokes: Moshpit does not read the prompt to confirm which option is highlighted. yes and continue are quick replies that type into the pane, not into a chat.

When the tap cannot land

  • On herdr, the frame channel types into whichever pane it is attached to, so a tap first sends agent focus to the target pane and waits for the retarget to finish before writing. This exists because of a real bug: with two agents running, a lock-screen Allow could send someone else's Enter.
  • If focus cannot be obtained, delivery returns false and you get a notification — "Not delivered", "Your tap didn't reach the agent — open Moshpit and answer there." A lock-screen Allow that silently fails is the worst outcome, so it is announced rather than swallowed.
  • If Moshpit knows the pane already left the "needs you" state, Allow and Deny are not sent at all: "Prompt already gone", "That request was already answered or has changed — nothing was sent." Stale "needs you" notifications are also pulled off the lock screen when the state changes, so you cannot answer a prompt that has been replaced by a newer, more dangerous one.
  • A session that was fully disconnected cannot be revived from a background tap. Open the app and answer there.

The four switches that control it

Settings → NOTIFICATIONS. All four default to on.

  • Notifications — "Alert when an agent needs you"
  • Live Activity — "Show agent session status in the Dynamic Island"
  • Alert sound — "Play a sound when the agent needs you"
  • Show detail on lock screen — "Display what the agent is running/asking — off keeps it private". With it off you still see working / needs you / done, just not the Bash: npm install line.

Turn both Notifications and Live Activity off and nothing is watched at all. On a herdr connection the Install agent hooks row is hidden, since there is nothing to install.

Dynamic Island showing an amber dot and an exclamation mark: an agent needs you

Unlocked · needs you The pill keeps an amber exclamation mark for one blocked agent, or the count for several. Only one agent fits in the pill — the Switch button in the expanded view cycles to the others.

Dynamic Island showing a teal dot and a live timer while an agent works

Unlocked · working Teal with a live timer while Claude Code is running. A thinking agent and a stuck one do not look alike.

The honest part

What happens when you background the app

This is the section to read before you rely on any of the above. It is the trade for having no server in the middle, and it is not something a setting can turn off.

iOS suspends the connection, and state stops updating

There is no push server anywhere in Moshpit. Every alert on your phone is a local notification the running app generated from the live session. When iOS suspends the app, the 2-second sweep and the SSH polls stop with it.

Measured on device: with Moshpit backgrounded, flipping a pane from blocked to working left the Dynamic Island unchanged. It only flipped one poll after returning to the foreground. The same is true on the tmux path — this is a product decision about not running a relay, not a herdr problem.

So the app says "paused" instead of lying

Moshpit                      2 working · 1 needs you
●  claude       needs you   2m
   mac-studio · ~ · Tab 1
   Bash: npm install

        [ Deny ]  [ Allow ]

… app suspended for two minutes …

Moshpit            paused — open Moshpit to refresh

The Live Activity carries a 120-second staleness horizon — two missed sweeps — and renders an honest "paused" hint rather than a frozen "working". The Home Screen widget uses 180 seconds and dims every state dot to grey when it goes stale.

Even in the foreground, out-of-band changes lag

  • The herdr control poll runs every 2 seconds while things are moving, and eases to 8 seconds after three identical reads. Any change, or a pull to refresh, snaps it back to 2 seconds.
  • Anything you do in the app refreshes immediately — every mutation is followed by a re-read. Anything done elsewhere (your laptop, another client, an agent changing state) can take up to 8 seconds to appear.
  • That is why elapsed times are printed in minutes — now, 2m, 1h 12m — and never in seconds. The data behind them is up to 8 seconds coarse, and printing seconds would claim a precision the number does not have.
  • herdr's socket API does have an event subscription, but its CLI exposes no subscribe command and a phone cannot open a Unix socket on your host. Polling is the only mechanism available today.

Two Moshpit clients on the same pane will fight

herdr's direct attach is exclusive per pane. Moshpit must attach with --takeover, because a reconnect always collides with its own stale channel. Two phones on the same pane therefore each re-claim it roughly every two seconds. That is inherent to exclusive direct attach and cannot be fixed from the client.

What Moshpit does about it: three unrequested closes inside 30 seconds trigger a 30-second pause and a visible notice — "Another client is using this pane — retrying shortly" — which clears itself as soon as frames paint again. A laptop running herdr's own TUI does not occupy an attach slot, so laptop-plus-phone should be safe, but that combination has not been tested.

Loose ends

Questions this guide usually raises

Do I have to use herdr?

No. On tmux, install the agent hooks from Settings → NOTIFICATIONS → Install agent hooks — one command that registers Claude Code's UserPromptSubmit, PreToolUse, Notification and Stop hooks so they stamp state onto the tmux pane. It backs up ~/.claude/settings.json first, always exits 0 so it cannot block the agent, and de-dupes itself if you run it twice. Without hooks, Moshpit falls back to reading pane output and the terminal bell, which is a guess and is described as one. The Agents section on Home is herdr-only.

Can it wake me when Moshpit is closed?

No. There is no push server, so alerts only exist while the session is alive on your device. iOS suspends the connection and state stops updating until you return. See the section above.

What about Mosh?

Mosh is the right choice for a shell you want to survive a Wi-Fi to 5G handoff, but it cannot carry herdr's frame protocol or tmux -CC. Over Mosh, herdr runs its own TUI inside the terminal and takes about a third of the phone's width for its sidebar; the control-plane sheets still work through a separate SSH sidecar. For this guide, use SSH. Details in Mosh and roaming.

Claude Code is running but the Agents section is empty.

It is almost certainly not running in a herdr pane. herdr identifies agents by watching its own panes, so a claude started in Terminal.app or a separate SSH session is invisible to it. Start it with New Agent Task, or run it inside a herdr pane. On herdr 0.7.3 there is a second effect: no agent name is reported, so an idle agent never grows a row at all.

Does Moshpit read my code, my prompts, or anything else?

There is no Moshpit account, no relay and no Moshpit server. The app connects only to servers you add. The one thing that leaves the pane is the hook's short title — what the agent is running or asking, capped at 80 characters — which is rendered on your own lock screen and can be turned off with Show detail on lock screen.

Where do I go next?

Connections and keys for the full form and host-key behaviour, Using herdr for the vocabulary and key bindings, and Answering agents for the notification and Live Activity path in detail. If something will not connect or behaves oddly, start at Troubleshooting.

PreviousIsolated tasks with git worktreesNextKeyboard and shortcuts