I Built a Remote to Answer Claude Code From My Phone
Hello there! If you run Claude Code or Codex for real work, you know the moment. You start a task, it runs for a while, and you go make a coffee. When you come back, the agent has been waiting ten minutes for you to answer one question. "The fix is in. What should I do with it?" That question is wh

Posted on Oct 18 • Originally published at farol.sh
If you run Claude Code or Codex for real work, you know the moment. You start a task, it runs for a while, and you go make a coffee. When you come back, the agent has been waiting ten minutes for you to answer one question.
"The fix is in. What should I do with it?"
That question is why I built Farol Remote. It lets you answer your coding agents from your phone, wherever you are, while the work keeps running on your Mac or on your own server.
Farol is a macOS terminal I made for coding agents. Its sidebar shows every session with a small dot: hollow when idle, breathing while the agent works, rippling when it waits for you, filled when it's done.
It tells you when an agent needs you. But only if you're at your Mac.
So the idea was simple. Put that sidebar in my pocket. See the sessions, read the conversation, and answer with a tap.
The iPhone app lists your sessions with their status and opens each one as its agent's conversation, not as a wall of terminal text.
When the agent asks for an approval or a question, the answers show up as buttons. One tap and it carries on. You can also reply with a slash command, send a screenshot for the agent to look at, review what changed file by file, and start a new session in a project.
It notifies you when an agent waits or finishes. And a Live Activity on the lock screen keeps count of the agents that work or wait, even with the app closed.
One phone can know several Macs and servers at once. Your work Mac and a server you run agents on show up in the same list, each session labeled with its host.
A phone and a Mac don't talk to each other directly over the internet. Your Mac sits behind a home router, your phone is on 4G, and neither can reach the other.
So there's a relay in between. Both sides connect out to it, and it passes messages along.
That raises the obvious question. If everything goes through a server, what can that server see? These messages are your code, your prompts, and a phone that can type into your terminal. I didn't want the answer to be "trust me".
The goal became this: the relay passes messages it cannot read, and keeps none of them.
It all starts with a QR code. On the Mac you turn on Remote in Settings and press Show code. On the phone you scan it.
The code carries three things: the relay's address, the Mac's public key, and a secret made just for that code. It's a link, and everything rides after the #.
func ( o offer ) link () string { text, _:= json. Marshal ( o ) return "https://farol.sh/pair#" + base64. RawURLEncoding. EncodeToString ( text ) } Enter fullscreen mode Exit fullscreen mode The part after the # is never sent to a server. So the secret never reaches the relay, which means the relay can't pair a device of its own.
The phone answers with one sealed message that proves two things at once: it holds the key it claims, and it saw the secret. A code works once and lasts two minutes. After that, the Mac knows the phone's key and the phone knows the Mac's.
Each time they connect, both sides say hello in the clear with their public key and a random number made for this connection.
Then each direction becomes its own HPKE stream, sealed to the other side's key and signed with ours. I used CryptoKit's Curve25519_SHA256_ChachaPoly suite. Here's the sending side on the Mac and the phone:
public mutating func connect ( to peer: ChannelHello, trusting trusted: [ Data ]) throws -> Data { guard trusted. contains ( peer. key ) else { throw RemoteError. unknownDevice } let sender = try HPKE. Sender ( recipientKey: Curve25519. KeyAgreement. PublicKey ( rawRepresentation: peer. key ), ciphersuite: Pairing. suite, info: info ( from: side, with: peer ), authenticatedBy: me. secret ) self. peer = peer self. sender = sender return sender. encapsulatedKey } Enter fullscreen mode Exit fullscreen mode The info includes both hello numbers. So a recording of an older connection doesn't open on a new one, and messages only open in the order they were sealed. If one fails to open, the connection ends.
It sees a public key, and a room name that's the hash of the host's key. That's how a Mac and its phones find each other without the relay learning who they are.
func room ( public [] byte ) string { sum:= sha256. Sum256 ( public ) return hex. EncodeToString ( sum [: ]) } Enter fullscreen mode Exit fullscreen mode Then it sees encrypted messages and how big they are. It stores none of it, and its log holds totals only.
This one was fun. I wanted a notification when an agent waits, even with the app closed. That means Apple has to push it, and something has to ask Apple.
The Mac asks the relay to forward a push. The relay sees the phone's notification token and one word: waiting or done. Which session it's about travels sealed, for the phone alone.
The Live Activity works the same way. The host keeps it current through the relay, so the counts on your lock screen move while the app sleeps.
Some people run their agents on a machine that stays on, so they can close the laptop. For them there's farol-host, a small Go CLI.
curl -fsSL https://farol.sh/host.sh | sh farol-host setup Enter fullscreen mode Exit fullscreen mode Setup connects the hooks for Claude Code and Codex, keeps farol-host running with systemd, and shows a QR code to scan. Then every tmux window on that machine is a session on your phone, like a tab in Farol on the Mac.
tmux new -s api -c ~/code/api claude Enter fullscreen mode Exit fullscreen mode One protocol, two languages The Mac and the iPhone share a Swift package. The server speaks the same protocol, written again in Go.
Two implementations of the same crypto and messages is a great way to have bugs that only show up on a real phone. So they check each other.
The Swift tests write sample messages and a sealed conversation to JSON files. The Go tests open those files and must read them exactly. When a message changes, I regenerate them:
FAROL_WRITE_WIRE = 1 swift test Enter fullscreen mode Exit fullscreen mode If Go and Swift disagree on a single field, a test fails before any phone sees it.
If a host's key were stolen later, traffic recorded from a phone to that host could be read. A host also doesn't prove itself to the relay yet, so someone who learned a room's name could knock the real host off. They couldn't read or send anything, but it's still on my list.
And the pairing and the encryption haven't had an outside review. If that's your thing, I'd love a second pair of eyes. It's all in one small repo, on purpose.
Farol is free and open source. The iPhone app is free too, in beta on TestFlight. In Farol, open Settings, choose Remote, turn it on, press Show code, and scan it from the app.
The security parts live in farol-remote: the Swift package, the relay, and farol-host. The security page goes deeper than this post.
Hope you find it useful! Feel free to open an issue or contribute. And next time your agent asks you a question while you're out, you can just answer it. ☕