The Claude Agent SDK integration
How the desktop runs Claude through a Node agent host: the JSON-lines pipe, the projection of Claude messages into the shared timeline rows, live runs and approvals, subagents, and accounts.
Checked against the source on 29 September 2026
On this page
The agent host process
Claude runs through the agent host, a small TypeScript program in desktop/agent-host that uses @anthropic-ai/claude-agent-sdk. The desktop starts it with Node and passes the path of the packaged Claude executable as its only argument. A development build runs node from the PATH with agent-host/dist/src/entry.js. A packaged app runs its bundled node with claude/host/entry.mjs from its resources.
The two sides exchange one JSON object per line over stdin and stdout. A request carries an id, and its reply carries the same id with a result or an error. A line without an id is an event: message, permission, permissionResolved, failed, or closed. The host checks every request against a strict schema, so an unknown field fails. The desktop gives each request 35 seconds.
| Request | What the host does |
|---|---|
| open | Starts one SDK query for the session in its project folder. It resumes the conversation when Claude has a saved transcript for that id, and otherwise starts a new one with that id. One host owns at most one session. |
| send | Writes the message into the open query with the desktop’s message id. It refuses while Claude is still busy. |
| permission | Answers a pending tool permission with allow or deny. |
| interrupt, close | Stops the turn, or closes the query and ends the host. |
| history, list, inspect | Reads saved conversations of the folder through the SDK. |
| subagents, subagentHistory, toolOutput | Reads saved subagent transcripts and the full output of one tool call. |
| catalog, usage, title | Reads the model list, the plan usage, or a short session title. |
Every run gets a new host, and the host never survives its run. When the run ends, the desktop sends close, closes stdin, and waits up to 10 seconds. Then it kills the host’s whole process group. Reads that need no open conversation, such as the model catalog or a title, run in a one-shot host of their own. Before the start, the desktop removes nine environment markers that a parent Claude Code session gives its children, among them CLAUDECODE, CLAUDE_CODE_CHILD_SESSION, CLAUDE_CODE_SESSION_ID, and CLAUDE_PID. A desktop that you start from inside Claude Code would otherwise run its sessions as child sessions, and Claude doesn’t save their transcripts.
The query loads user, project, and local settings, uses the claude_code system prompt, and saves the session. The host sets CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS, because a turn that starts background subagents ends before they do. Only the idle state tells the host that the whole run is over.
History and the shared timeline rows
The desktop never reads Claude’s transcript files itself. The host reads them with getSessionMessages and returns pages of at most 100 complete messages. It joins the SDK envelopes of one API message into one message and drops messages with nothing to show. A page boundary is a message uuid, and when that message is gone the reply asks the desktop to reload. The SDK returns a compacted conversation from its last compaction on, so older turns of such a conversation don’t appear in HB Code. A command’s full output is read on demand through toolOutput, the same way a Codex protocol output is read.
To continue an existing Claude Code conversation, the desktop lists the saved conversations of exactly that workspace folder, newest first. A conversation belongs to at most one HB Code session. Choosing it again opens that session instead of a second one.
The Claude adapter projects each message into the same timeline rows that Codex uses. A live row and its saved row take their identity from the API message id or the saved uuid, so the saved row replaces the live one in place.
| Claude message or block | Timeline row |
|---|---|
| Bash tool call and its result | A command row with its output. The output row names the saved result, which is read only when you open it. |
| Edit, MultiEdit, Write, NotebookEdit | A file change row. Claude gives the old and the new text, so the adapter builds the unified diff. Each hunk has a bare @@ header, because Claude gives no line numbers. |
| Read | A tool row titled with the file name. Its detail shows the file text and opens the file. |
| Any other tool | One row that its result completes in place: Tool call, then Tool output, Tool error, or Tool declined. It shows a readable label, and an MCP tool shows as “server: tool”. |
| TodoWrite, EnterPlanMode, ExitPlanMode | A plan with its steps, or a plan event row with the plan as Markdown. |
| Image in a tool result | An image row after the tool row. |
| Thinking | A Reasoning row. Empty thinking makes no row. |
| Interrupt note | A turn aborted marker. |
| Compaction summary | A “Conversation compacted” marker. |
| Local slash command | Its name and its output. Terminal housekeeping such as /exit is hidden. |
| Message from another agent | An agent communication row. |
| Reply that Claude writes itself, such as a usage limit or an API error | A system row. |
| Synthetic user message (isSynthetic) and other meta rows | Hidden. |
| Subagent spawn call | A spawn row with the subagent’s status and a link to its own view. |
Live runs, approvals, and failures
agents/dispatch sends a queued message to the adapter of the session’s agent. A Claude session can’t steer a running turn, replace the latest turn, or use a speed mode. A steer request is rejected before it reaches the host, so a message waits in the queue or goes after you stop the run. A session has one active Claude run at a time.
The run opens the host with the session’s model, effort, and permission mode, records the operation, and then sends the text. Claude’s live stream never echoes the prompt, and a stdin write or a delta is never proof of delivery. Claude saves the user message before it calls the model. So the first frame after send starts one transcript read, and a frame that names the message in user_message_uuid starts one more if the first read came too early. When a read finds the message, the saved row appears first and the outbox row goes after it. At run end, one more read is the final proof, also for a stopped or failed run. A message that no read finds is never sent again automatically.
| Access level | Claude permission mode |
|---|---|
| read-only | plan |
| full-auto | acceptEdits |
| full-access | auto. The open fails when the model doesn’t support Auto mode. |
| unrestricted | bypassPermissions |
When Claude asks to use a tool, the host holds the SDK’s canUseTool promise and sends a permission event. The desktop shows the same approval card as for Codex, with Accept and Decline. A Bash call is a command approval, and a subagent’s request names the subagent. A phone answers through run.chatResolveApproval, and the dispatch lets the Claude adapter answer first when it holds that request id. A stop denies every open request with “Run stopped.”, and the end of the run marks the remaining cards as cancelled. AskUserQuestion is turned off, and Claude asks its questions in the chat reply.
A failed run keeps its reason in the desktop error log under claude_run, with the session, run, and operation ids. Packaged apps discard standard error, so the log line alone would be lost. The reason comes from Claude’s errors list, or from its result text for API errors such as a missing sign-in.
Subagents as child threads
Claude subagents use the same list, timeline, and live paths as Codex child threads. During a run, Claude reports each subagent through task events and forwards its turns with the id of the spawning tool call. The adapter sends those turns to the subagent’s own view, and the parent shows one spawn row. A subagent that the conversation started has depth 1, and each nesting adds 1.
After the run, the saved transcripts are the only source. The host reads them with getSubagentMessages, and a finished transcript is read once per desktop run. Subagents that are still open when the run ends stop with it.
Accounts and usage
The default account is this computer’s Claude sign-in and runs with Claude’s own folder. Each added account is a Claude folder in HB Code’s application data. It keeps its own sign-in and links every other entry to the main folder. All accounts therefore read and continue the same sessions with the same settings, and a switch changes only who pays for a turn. The next turn runs on the active account, and an account stays while a running turn uses it.
HB Code runs Claude’s own sign-in commands for one account folder. It never reads a token or runs its own authorization flow. Usage comes from the SDK: the rolling five-hour window and the weekly windows. The weekly window for third-party apps is left out. An API-key account has no plan windows. The sidebar, the settings page, and the phones share one reading per account per minute, and a finished run clears that account’s reading.