Storage ownership
Who owns which database and folder for Codex and for Claude, why HB Code never opens an agent’s files itself, which tables link a session to its agent, what a session delete removes, and the atomic identity claim behind the outbox.
Checked against the source on 5 October 2026
On this page
Separate the stores by owner
Each agent saves its own conversations. HB Code stores the link from a session to the agent’s conversation, the outbox, and live state. It reads a conversation only through the agent’s own interface: the Codex app-server protocol, or the Claude Agent SDK in the agent host.
| Question | Codex | Claude |
|---|---|---|
| Where is the transcript? | In the profile home codex/ | In the main Claude folder under projects. The main folder is CLAUDE_ |
| Which identity links the session? | codex_ | claude_ |
| Who reads history? | The Codex app-server child of the profile, on thread/ | An agent host process that starts for one request. It calls get |
| storage | indexed | native |
| Where is command output? | The desktop appends the live stream to a file in codex-command-outputs and catalogs it in codex_ | Only in the transcript. HB Code stores no output byte. Each read of a Bash result sends the host request tool |
| What does HB Code store itself? | codex_ | claude_ |
- HB Code dataappdata.db · codex-command-outputs/ · pasted-images/
The desktop runtime opens these itself: SQLx for the database, plain file reads and writes for Codex command output and chat attachments.
- Codex homecodex/profiles/<uuid>/home/
HB Code creates the folder, launches Codex with it as CODEX_HOME, and deletes the whole profile folder when you remove the profile, unless a session still links a rollout inside it. It never opens a file inside. History comes back through thread/items/list.
- Startup guard--allowed-storage-root = CODEX_HOME = sqlite_home
The bundled app-server refuses to start unless all three resolve to the same folder, so Codex can’t keep its databases anywhere else.
- Claude account folderclaude/accounts/<uuid>/
One folder for each account you add. It keeps .claude.json, its lock file, .credentials.json, backups, and statsig. HB Code links every other top-level entry of the main folder into it and never replaces an entry that the folder already has.
- Main Claude folder~/.claude · CLAUDE_CONFIG_DIR
Claude Code owns it. All accounts save their transcripts here through the projects link. The agent host reads them with the Claude Agent SDK, and every history read starts the host with this folder, whichever account is active.
The figure shows the desktop computer only. Two stores are off the desktop: the server database and the phone. The last two rows of the next table name what each holds. Neither holds a transcript.
| Store | Owner and content | Access rule |
|---|---|---|
| HB Code appdata. | Desktop product metadata, session links for both agents, outbox operations, remote idempotency, live overlay state, and plan-limit readings. | HB Code Rust repositories use SQLx and SQLite. |
| App-owned Codex profile home | The Codex child owns its private databases, auth/ | Use the app-server protocol. HB Code must not query the child’s private SQLite schema. |
| An explicitly selected Codex conversation source | The exact linked transcript path and thread identity define the source. | Access the selected source through path-aware app-server methods. Do not search other homes for a replacement. |
| Main Claude folder (~/ | Claude Code owns its saved transcripts, settings, and sign-in. | The desktop runtime never parses a transcript. The agent host reads transcripts through the Claude Agent SDK. |
| Claude account folder (claude/ | The sign-in of one added account: .claude. | HB Code creates the folder and the links and removes the folder with the account. Removal deletes the links and never follows them. |
| Server Postgre | Account and service data controlled by server repositories and database roles. | Separate migration, system-runtime, and tenant-runtime principals. |
| Phone state | View state, local copies of unsent messages, and preview and output caches. | Treat the desktop as authority for projects, execution, outbox, and files. |
Every Claude transcript read runs with the main folder, whichever account is active. A saved transcript needs no sign-in, and every account saves into the same projects folder. HB Code creates that folder in the main folder before it links an account. If the projects link fails, the account setup fails with an error. A failed link for any other entry only writes a warning to the log.
The agent host opens three kinds of Claude files directly, because the SDK does not return them: the meta.json file beside a subagent transcript, the workflow run record, and the effort field of a subagent’s first saved assistant turn. It reads them and never writes them.
The WebView asset protocol is scoped to media previews under the app cache only, so no private runtime file is reachable from the page.
Storage identity and execution identity differ
The column sessions.chat_agent holds codex or claude. It is set when the session is created, and a session update keeps it. It decides which identity tables a session uses.
- sessions.chat_agentcodex · claude
Set when the session is created and not changed by a later update. It decides which side a session has. The column default is codex.
- codex_session_linksapp_session_id → profile_id, codex_thread_id, rollout_path, status
A partial unique index allows one available link for each profile and thread. The status is available, profile_required, or unavailable. The profile_id foreign key restricts deletion of a profile row that a link still names.
- claude_sessionsapp_session_id → native_session_id (unique)
One app session owns one Claude conversation. A second session that tries to continue the same conversation fails with “This Claude conversation is already open in another session.”
- claude_operations(app_session_id, operation_id) → native_message_id, run_id, display_text, attachments
Written before the send. A retry of the same operation keeps the first UUID and the first run ID. A retry with a different text or different attachments fails with a conflict.
An app-owned Codex profile has a stable opaque UUID in canonical lowercase text. An email address and a filesystem path are not profile identity. The codex_profiles table holds one row for each profile with a status of signed_out, authenticated, or unavailable and the account columns that the app-server reported.
Indexed and Exact are design names for the two Codex read routes. In code one predicate decides: a link whose rollout path lies outside the runtime home is an external exact source. Both routes call thread/items/list on the Codex app-server. An exact read adds path and historySources. The wire storageFormat of a Codex session stays indexed on both routes. An operation resolves its route once and keeps it.
A Claude session has no profile link, because every Claude account reads the same transcripts. HB Code does not store whether a transcript exists. The host answers missing for a conversation that Claude has not saved yet, and the desktop then shows an empty timeline. The UUID in claude_operations is the UUID of the user message that the desktop sends, which is how the desktop finds that message in the transcript. The row also keeps the message text and the attachments.
Exact source access has deliberate limits
- historySources[{ rolloutId, path }]
Built by the desktop from the absolute paths of the session’s other linked rollouts, which it records as Codex announces each child. It’s the only place the app-server looks for an ancestor.
- history_basethread_id · end_byte_offset
Session metadata at the top of the child’s file: the ancestor rollout and the byte where the inherited part ends. The app-server follows up to 256 ancestors and rejects a cycle or an ancestor shorter than that offset.
- reloadRequirederror data.code
The cursor carries a digest of every byte it covered. Appending keeps it valid. Changing those bytes fails the next page, which the desktop shows as an ordinary load error. Only sourceUnavailable offers to choose the source file again.
| Condition | Contract |
|---|---|
| Required ancestor source is absent | The app-server fails the read with missing |
| Source is replaced or truncated | The app-server binds each cursor to a digest of the bytes it read. Appending keeps cursors valid. Truncating or rewriting fails the next page with reload |
| Current method takes only a thread ID | Do not offer that mutation for an exact external source if it cannot address the selected path. This applies to current goal, rollback, and persisted thread-settings operations. |
| Another bundled sidecar owns the writer | Release thread ownership before a different runtime resumes it. |
Before a send or an attach, the desktop releases the thread from every other profile server it runs. That coordinates the bundled sidecars of this process. It does not prevent an unrelated external Codex process from writing the same source. Stop an external writer before you continue that conversation in HB Code.
The outbox uses an atomic identity claim
workspace_chat_outbox
session_id → entries_json, updated_at
workspace_chat_outbox_operation_identity
(session_id, operation_id) → request_fingerprint
remote_rpc_idempotency
scope_key → request_fingerprint, state, response_json, lease, expiry
chat_operation_ledger
(app_session_id, operation_id) → status, result_json
chat_session_messages
operation_id → recipient, sender, sender_call_id, sender_run_id, sender_thread_id, chain_id, hop, delivery
chat_live_timeline_overlays
(app_session_id, timeline_target_id) → revision, snapshot_json
chat_live_timeline_overlay_revisions
singleton → one global revision sequence
chat_pending_terminal_overlay_cleanups
(app_session_id, run_id)
agent_usage_samples
agent, account_key, window_kind → used_percent, resets_at, sampled_atFive tables carry the chat_ prefix, and they serve Codex and Claude sessions alike. Four of them had the codex_ prefix until a rename migration. chat_session_messages is newer. Tables that only Codex uses keep the codex_ prefix, for example codex_session_links and codex_command_outputs.
| Table | Content | Rule |
|---|---|---|
| chat_ | The status and the stored result of one chat operation. | Key: app session ID and operation ID. A session delete removes the rows of the base session, of its :: |
| chat_ | The record of one message that an agent in one session sent to another session. | Key: operation ID. The pair of sender session and sender call ID is unique, and hop is at least 1. delivery is sent, needs-approval, or declined. |
| chat_ | The saved live overlay of one timeline as JSON. | Key: app session ID and timeline target ID. For Claude the target is the app session ID for the conversation and the subagent ID for a subagent. |
| chat_ | One row with one revision counter for all overlays. | The counter only grows, up to 2^53 − 1. A new target cannot reuse the revision of a deleted target. |
| chat_ | The ended runs whose live rows are not yet fully replaced by saved history. | Key: app session ID and run ID. The row is written before the desktop forgets the active run and stays until every timeline of that run is reconciled. |
save_workspace_chat_outbox_with_operation_identity opens one transaction. Its INSERT … ON CONFLICT updates the identity row only when the existing fingerprint equals the submitted fingerprint. If that statement does not affect one row, the transaction rolls back. It then upserts the outbox entries JSON. For a message from another session it also inserts the chat_session_messages row, and a row with the same operation ID stays as it is. Then it commits. A session message is never queued without its record and never recorded without its queue entry. The identity row is keyed by the base session while the outbox JSON is saved under the alias session, so the same operation can be found from any alias in the scope.
The remote_rpc_idempotency table is the second ledger. It keeps the outcome of every listed remote mutation for 48 hours across desktop restarts, so a phone that retries after the desktop came back still gets the stored answer.
agent_usage_samples keeps plan-limit readings for both agents. The account key is the active Codex profile ID, or the Claude account ID, which is default or a UUID. A reading is skipped when the newest reading of the same window is younger than 10 minutes, has the same reset time, and differs by less than 0.5 percent. Readings older than 35 days are deleted.
What a session delete removes
delete_session_with_scoped_references removes the HB Code rows of a session in one SQLite transaction. The delete sends no request to Codex and none to Claude. The agent’s transcript stays where the agent saved it.
| Data | Effect of the delete |
|---|---|
| codex_ | Deleted in the transaction. Their pending append rows go with them. The output files are removed after the commit. |
| chat_ | Rows of the base session, of its :: |
| codex_ | Rows with this base session are deleted. |
| workspace_ | Rows of the same three scopes are deleted. |
| project_ | active_ |
| sessions | The row is deleted last. |
| claude_ | Deleted by ON DELETE CASCADE from the sessions row. A session message record goes when its sender or its recipient is deleted. |
| Memory of the running desktop | After the commit the desktop clears the cached outbox entries, the video analysis entries, and the in-memory operation ledger of the session. |
| Codex thread and rollout files | Never touched. They stay in the profile home or at the attached path. |
| Claude transcript | Never touched. It stays in the main Claude folder, and a new session can continue it. |
Project file access is a separate boundary
Remote file reads run with the desktop user’s filesystem access. resolve_desktop_file_read_path expands a home shortcut, joins a relative path onto the canonical project directory, and canonicalizes the result. It does not enforce project containment, and the design note says so on purpose. allowExternalFile and allowExternalMedia only decide which resolver is tried first, so an authenticated companion can open paths outside the selected project either way. ensure_path_within_project, which rejects parent components and checks the canonical root, exists for desktop-local operations.
App-managed chat attachments live in pasted-images under the app data directory, and an external media read for an attachment must classify as image, video, audio, or document. The phone never uses its own home directory.