zed¶
Zed (catalog agent zed) is a code editor whose built-in agent panel runs threads with the
native Zed Agent and drives external agents over ACP, the Agent Client Protocol, the JSON-RPC
protocol Zed uses to drive an external agent (Claude Agent, Gemini CLI, Codex) as a child process.
It was researched together with Goose (goose.md). The analyzer parses the panel's
threads in threads.db, the agent tables and key-value namespaces of db.sqlite and the standing
permission entries of the user settings files, from source and fixtures only.
Source and evidence¶
zed-industries/zed, open source, at
a84689073d296dfd39987bc7dd478e43ef76d83a
(blobless partial clone); agent crates GPL-3.0-or-later
(crates/agent/Cargo.toml:6,
crates/agent_ui/Cargo.toml:6).
Rust; sqlez (Zed's own SQLite wrapper), zstd crate, agent-client-protocol-schema 1.9.1. Platform
mapping verified against the dirs 6.0.0 crate (the version in Cargo.lock). Flatpak sandbox
paths are Flatpak documentation level. Paths cited are relative to the clone.
Observed¶
No real install. Every layout the parser handles (threads.db, the thread blob of
Record schema and sidebar_threads) and the bad-record rows of
Bad-line rows rest on source at
a846890
and 9cd14da
and on a synthetic fixture.
Source reviews¶
- 2026-10-21: zed-industries/zed at
9cd14da: the plan lines of Parser plan for standing permissions, denied calls, scratch directories, draft prompts, archived worktrees, sidebar terminals and the native sidebar rows now describe what the parser does, source-derived (issue 196). The review found that a project's.zed/settings.jsoncannot hold the permission keys, added the override blocks of a user settings file, the remaining automatic denial texts, the draft prompts' ACP block shape and the Source host rows statement. - 2026-10-19, zed-industries/zed at
9cd14da(the default branch that day): the paths of Per-user storage and the shapes the parser reads were unchanged, with line numbers moved by one or two (Format versions). The review added the permission settings and project trust, the denied-call results, the otherdb.sqlitetables, the scratch directories, the delete behaviour and the external agents' ids, with the plan lines of Parser plan that read them, and the bad-record rows the parser now yields (issue 168). - 2026-10-17, zed-industries/zed at
56934ec: a sync check that added Sync, relay and origin rows, Telemetry log and crash reports and the clipboard actions of Format versions, whose citations are to that commit.
Collection¶
Per-user storage¶
APP_NAME is Zed, lowercased for XDG paths
(crates/paths/src/paths.rs:18-21).
- Config
(
paths.rs:122-139): Windowsdirs::config_dir()/Zed=%APPDATA%\Zed(dirs/src/win.rs:8); Linux/FreeBSD$FLATPAK_XDG_CONFIG_HOMEor$XDG_CONFIG_HOME/~/.config, then/zed(dirs/src/lin.rs:9); macOS~/.config/zed. - Data
(
paths.rs:144-165): macOS~/Library/Application Support/Zed; Linux$FLATPAK_XDG_DATA_HOMEordirs::data_local_dir()=$XDG_DATA_HOMEor~/.local/share, then/zed(lin.rs:11-12); Windowsdirs::data_local_dir()/Zed=%LOCALAPPDATA%\Zed(win.rs:11). - State
(
paths.rs:169-190): macOS~/.local/state/Zed(capital Z), Linux~/.local/state/zed, Windows%LOCALAPPDATA%\Zed. No caller outside the paths crate uses it today. - Temp/cache
(
paths.rs:193-218): macOS~/Library/Caches/Zed, Windows%LOCALAPPDATA%\Zed, Linux~/.cache/zed. - Logs
(
paths.rs:228-255): macOS~/Library/Logs/Zed, elsewhere<data>/logs; filesZed.log,Zed.log.old. macOS crash reports in~/Library/Logs/DiagnosticReports(:264-268). --user-data-dir <dir>(crates/cli/src/main.rs:524,969) makes<dir>the data dir and<dir>/configthe config dir (paths.rs:103-125).- Flatpak builds use
FLATPAK_XDG_*(paths.rs:131,153,177,209); app idsdev.zed.Zed,dev.zed.ZedPreview,dev.zed.ZedNightly,dev.zed.ZedDev(script/flatpak/bundle-flatpak:15-33), so sandboxed state is~/.var/app/dev.zed.Zed*/{config,data,cache}/zed.
Config dir files: settings.json, global_settings.json, settings_backup.json, keymap.json,
keymap_backup.json, tasks.json, debug.json, AGENTS.md
(paths.rs:278-326),
themes/, snippets/
(:372-380),
development_credentials
(crates/zed_credentials_provider/src/zed_credentials_provider.rs:116),
git/config. On macOS only, prompts/ (holding the LMDB prompts-library-db.0.mdb,
crates/prompt_store/src/prompt_store.rs:28),
prompt_overrides/ and embeddings/ sit under config; elsewhere under data
(paths.rs:386-433).
Data dir: threads/threads.db agent threads
(crates/agent/src/db.rs:444-446);
db/0-<channel>/db.sqlite where channel is dev, nightly, preview, stable or global
(crates/db/src/db.rs:138,148-166;
crates/release_channel/src/lib.rs:218-221);
extensions/, remote_extensions/ (with uploads/), languages/, debug_adapters/,
external_agents/, copilot/, prettier/, remote_servers/, devcontainer/, hang_traces/,
server_state/
(paths.rs:222-242,348-483);
node/ runtime
(crates/node_runtime/src/node_runtime.rs:637,1031);
build_timings/
(crates/zed/src/reliability.rs:465);
zed-<channel>.sock
(crates/zed/src/zed/open_listener.rs:413);
logs/ on Linux and Windows. No conversations/ directory exists in current source.
db.sqlite also holds the agent panel's other tables and key-value namespaces, listed in
Other db.sqlite tables. Global agent skills are read from
~/.agents/skills/<skill>/SKILL.md, a directory shared with other agents and not created by Zed
(crates/agent/src/agent.rs:616-638).
Scratch directories¶
Zed has two, and the catalog collects neither:
- On macOS only, a thread whose terminal runs in the OS sandbox gets
tempfile's$TMPDIR/zed-agent-terminal-<random>(under/var/folders, outside the home), kept for the thread's life as itsTMPDIRand removed when the thread is deleted; the thread blob inthreads.dbrecords it assandboxed_terminal_temp_dir(crates/agent/src/thread.rs:1540-1566;crates/agent/src/db.rs:680-700). The directory is a writable path in the Seatbelt sandbox (macOS's per-process sandbox profiles); Linux uses bubblewrap's private/tmpand Windows the WSL sandbox's, so neither gets one (agent.rs:3851-3870). - A thread started in a new worktree runs in a linked git worktree (a second checkout made with
git worktree add) that Zed creates under thegit.worktree_directorysetting, by default../worktreesrelative to the repository, giving<project parent>/worktrees/<worktree name>/<project>; the setting must stay inside the project or its parent (crates/project/src/git_store.rs:6332-6349,10845-10898;assets/settings/default.json:1885-1901). The worktree path becomes the thread'sfolder_paths, with the real project insidebar_threads.main_worktree_paths. It is user project content beside the project, so no catalog line can name it; where it is recorded is in Where the project path is recorded.
Credentials¶
Production builds store credentials only in the OS keychain
(zed_credentials_provider.rs:56-64,69).
On the Dev channel, unless ZED_DEVELOPMENT_USE_KEYCHAIN is set, they go to
<config>/development_credentials as JSON
(:50-62,116-120).
settings.json (and its settings_backup.json copy and global_settings.json) can embed api_key
and custom_headers for LM Studio style providers
(crates/settings_content/src/language_model.rs:310-312)
and agent_servers environment. db.sqlite and threads.db hold no tokens.
The GitHub Copilot model provider runs its own GitHub OAuth device flow with Zed's client id and
the read:user scope
(crates/copilot_chat/src/copilot_oauth.rs:20-23)
and keeps the token through the same credentials provider under the URL
https://<oauth domain>/copilot-agent, so in the keychain, or in development_credentials on the
Dev channel
(crates/copilot_chat/src/copilot_chat.rs:50-52,
562-571,
678).
The settings.json keys agent.tool_permissions and agent.sandbox_permissions hold rules and
paths, not secrets, and the scoped_kv_store namespaces read at
9cd14da hold no token.
Exclusions¶
Under either data root: extensions, remote_extensions, node, languages, copilot,
debug_adapters, external_agents, prettier, remote_servers, devcontainer, embeddings,
server_state, hang_traces, plus build_timings. ~/.cache/zed and ~/Library/Caches/Zed are
temp. prompts/*.mdb is the user prompt library, small, keep.
Project-local files¶
.zed/settings.json, .zed/tasks.json, .zed/debug.json
(paths.rs:487-531);
rules files read by the agent: .rules, .cursorrules, .windsurfrules, .clinerules,
.github/copilot-instructions.md, AGENT.md, AGENTS.md, CLAUDE.md, GEMINI.md
(crates/prompt_store/src/prompts.rs:22-32).
No per-project database.
Where the project path is recorded¶
threads.dbtablethreadscolumnsfolder_paths,folder_paths_order(crates/agent/src/db.rs:469-470,545).db.sqlitetablesidebar_threadscolumnsfolder_paths,folder_paths_order(crates/agent_ui/src/thread_metadata_store.rs:1375-1383) and tableworkspacescolumnslocal_paths_array,local_paths_order_array(crates/workspace/src/persistence.rs:731-733).sidebar_threads.main_worktree_pathsandmain_worktree_paths_order: the main repository of a thread whosefolder_pathsis a linked worktree (crates/agent_ui/src/thread_metadata_store.rs:1387-1388).archived_git_worktreescolumnsworktree_pathandmain_repo_path(thread_metadata_store.rs:1390-1398).scoped_kv_storenamespacecreated_git_worktrees, keylocal, a newline, then the worktree path: the worktrees Zed made, which are scratch rather than projects (crates/git_ui_core/src/created_worktrees.rs:34-50).trusted_worktrees.absolute_path: every project the user trusted (crates/workspace/src/persistence.rs:986-991).sidebar_terminal_threads.working_directoryandfolder_paths(crates/agent_ui/src/terminal_thread_metadata_store.rs:483-500).
Every entry after the first is in db.sqlite. All are SQLite columns, which the collector's
project discovery does not read.
Parsing¶
Transcript stores¶
<data> is the data directory of Per-user storage.
| Store | Path | Unit |
|---|---|---|
| Threads database | <data>/threads/threads.db |
threads row = one thread; data BLOB = zstd(JSON) of the whole thread including all messages (crates/agent/src/db.rs:444-457,535) |
| Sidebar metadata | <data>/db/0-{stable,preview,nightly,dev}/db.sqlite, table sidebar_threads |
one row per thread, including external-agent threads (crates/db/src/db.rs:138,166; release_channel/src/lib.rs:216-223; agent_ui/src/thread_metadata_store.rs:1375-1465) |
No separate text-thread or contexts/*.json store exists at this commit.
An external agent's transcript is in no Zed store: Zed asks the agent to replay it over the ACP
session/load request
(agent_servers/src/acp/v2.rs:1173),
so it lives wherever that agent keeps its sessions. The ids of the three built-in external agents,
the sidebar_threads.agent_id values, are gemini, claude-acp and codex-acp
(agent_servers/src/custom.rs:17-19).
What survives a delete¶
Deleting a thread (the archive view's delete, or the native connection's delete_session) removes
its threads row, every subagent row whose parent_id chain leads to it and the macOS sandbox temp
directory its blob names
(crates/agent/src/db.rs:680-754;
agent.rs:3613-3625),
then its sidebar_threads row and its draft prompt, then the archived-worktree rows and their
refs/archived-worktrees/<id> ref once no other thread links to them
(crates/agent_ui/src/threads_archive_view.rs:814-854).
"Delete all" empties threads
(db.rs:756-789),
and whether the sidebar rows of a thread removed that way outlive it was not determined.
Archiving only sets sidebar_threads.archived; for a thread in a Zed-created worktree it also
removes the worktree directory after saving its state as commits
(crates/agent_ui/src/thread_metadata_store.rs:859-878).
A per-prompt history file is not stated. The created_git_worktrees entry of a thread's Zed-created
worktree survives the deletion, and whether a sidebar terminal row does was not determined. An
external agent's thread lives in that agent's own store, and Zed asks the agent to delete it over
ACP session/delete only when the agent offers it
(crates/agent_servers/src/acp/v1.rs:285-300),
so otherwise it stays there. A copy already sent with a rating stays with Zed
(Sync, relay and origin). Nothing local records a deletion. The
analyzer reads threads.db and every store of Other db.sqlite tables, so
a deleted thread's surviving created_git_worktrees entry still shows; none of these stores
repeats a thread's messages, so none is a secondary source (see
Secondary sources).
Record schema¶
threads table¶
threads.db
(crates/agent/src/db.rs:450-485):
CREATE TABLE threads (id TEXT PRIMARY KEY, summary TEXT NOT NULL, updated_at TEXT NOT NULL,
data_type TEXT NOT NULL, data BLOB NOT NULL);
ALTER TABLE threads ADD COLUMN parent_id TEXT;
ALTER TABLE threads ADD COLUMN folder_paths TEXT; ALTER TABLE threads ADD COLUMN folder_paths_order TEXT;
ALTER TABLE threads ADD COLUMN created_at TEXT;
id is the ACP session id, a UUID v4 string
(thread.rs:1430;
#[serde(transparent)] newtype,
agent-client-protocol-schema-1.9.1/src/v1/mod.rs:48-51).
parent_id is the subagent parent
(db.rs:514-517).
summary is the title. updated_at and created_at are to_rfc3339(), for example
2026-01-02T03:04:05.123456789+00:00
(db.rs:513,542).
folder_paths is a PathList, sorted paths joined by \n; folder_paths_order is
comma-separated display indices
(crates/util/src/path_list.rs:130-146).
data_type is zstd or json
(db.rs:363-367).
Thread blob¶
Blob JSON is DbThread flattened plus "version":"0.3.0"
(db.rs:53-89,189,505-510):
title, messages[], updated_at (RFC 3339 "Z"), detailed_summary, initial_project_snapshot{worktree_snapshots:[{worktree_path, git_state:{remote_url, head_sha, current_branch, diff}}], timestamp}, cumulative_token_usage, request_token_usage{<user_msg_id>:{input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens}}, model{provider, model}, profile, subagent_context{parent_thread_id, depth}, speed, thinking_enabled, thinking_effort, draft_prompt, ui_scroll_position, sandboxed_terminal_temp_dir, sandbox_grants{write_paths, network_hosts, ...}
(agent.rs:89-93;
project/src/telemetry_snapshot.rs:33-45;
legacy_thread.rs:45-49;
language_model_core.rs:525-535).
The blob holds one model per thread, overwritten on a switch, and no message records a model.
sandbox_grants is DbSandboxGrants, the sandbox escalations the user approved "for the rest of
this thread" (the "Allow for this thread" prompt option): write_paths (granted write paths, each
covering its subtree), network_hosts (host patterns such as *.npmjs.org), and the booleans
network_any_host, allow_fs_write_all, unsandboxed and sandbox_fallback (commands allowed to
run unsandboxed because the OS sandbox could not be created), all #[serde(default)]
(db.rs:84-122).
A write_paths entry is a bare path string when no canonical target was resolved (and in legacy
rows), else an object {requested, resolved, on_windows_fs?}
(granted_write_path.rs:64-87).
profile is the agent profile id the thread ran under
(db.rs:69).
Messages¶
messages[] are externally tagged serde enums with no renames
(thread.rs:202-208,287-301,743-759):
{"User":{"id":"<uuid>","content":[{"Text":"..."},{"Mention":{"uri":...,"content":"..."}},{"Image":{"source":"<base64 png>"}}]}}{"Agent":{"content":[{"Text":"..."},{"Thinking":{"text":"...","signature":null}},{"RedactedThinking":"..."},{"ToolUse":{"id":"toolu_1","name":"terminal","raw_input":"{...}","input":{"type":"json","value":{...}},"is_input_complete":true,"thought_signature":null}}],"tool_results":{"toolu_1":{"tool_use_id":"toolu_1","tool_name":"terminal","is_error":false,"content":[{"Text":"..."}],"output":{...}}},"reasoning_details":null}}(language_model_core.rs:592-629;request.rs:67-76,148-152; a legacy single-valuecontentis accepted,request.rs:104-145)"Resume",{"Compaction":{"Summary":"..."}}or{"Compaction":{"ProviderNative":{...}}}.
Tool results of denied calls¶
At
9cd14da
an approval leaves no record in the thread. A denial fails the tool, and the error text becomes the
tool_results entry with is_error true
(thread.rs:3958,
5501-5512):
Permission to run tool denied by user for the user's denial
(thread.rs:6905),
and for an automatic denial by the rules Blocked by global default: deny,
Command blocked by security rule for <tool> tool, <tool> tool is disabled or the built-in
Blocked by built-in security rule
(crates/agent/src/tool_permissions.rs:12-13,
292-298,
396-430),
and three more automatic texts: The terminal command could not be approved because ... for a
command with shell substitutions, The <tool> tool cannot run because <n> regex pattern(s) failed to
compile. ... and The <shell> shell does not support "always allow" patterns ...
(tool_permissions.rs:14-17,
318-323,
447-460).
The error becomes one Text content item holding the error's message
(crates/language_model_core/src/request.rs:251-255).
A call cut off by the user is Tool canceled by user
(thread.rs:72).
sidebar_threads and archived worktrees¶
db.sqlite → sidebar_threads (final shape,
thread_metadata_store.rs:1424-1437,1459-1464):
CREATE TABLE sidebar_threads(thread_id BLOB PRIMARY KEY, session_id TEXT, agent_id TEXT, title TEXT NOT NULL,
updated_at TEXT NOT NULL, created_at TEXT, folder_paths TEXT, folder_paths_order TEXT, archived INTEGER DEFAULT 0,
main_worktree_paths TEXT, main_worktree_paths_order TEXT, remote_connection TEXT) STRICT;
ALTER TABLE sidebar_threads ADD COLUMN interacted_at TEXT; ALTER TABLE sidebar_threads ADD COLUMN title_override TEXT;
CREATE TABLE archived_git_worktrees(id INTEGER PRIMARY KEY, worktree_path TEXT NOT NULL, main_repo_path TEXT NOT NULL,
branch_name TEXT, staged_commit_hash TEXT, unstaged_commit_hash TEXT, original_commit_hash TEXT) STRICT;
CREATE TABLE thread_archived_worktrees(thread_id BLOB NOT NULL, archived_worktree_id INTEGER NOT NULL
REFERENCES archived_git_worktrees(id), PRIMARY KEY (thread_id, archived_worktree_id)) STRICT;
thread_id is 16 raw UUID bytes
(sqlez/src/bindable.rs:360-370);
session_id equals threads.id; agent_id NULL means the native Zed Agent, otherwise an external
ACP agent whose transcript is not in threads.db
(thread_metadata_store.rs:1499-1500,1725-1727;
agent.rs:2742);
times are RFC 3339
(:1729-1740);
remote_connection is JSON, the SSH, WSL or Docker remote-development target, not a cloud id
(remote_client.rs:1361-1367);
interacted_at is the last user send.
Joins and timestamps¶
sidebar_threads.session_id = threads.id (plus threads.parent_id for
subagents). Tool pairing inside one Agent message: content[].ToolUse.id
with the tool_results key or tool_use_id. Project path:
threads.folder_paths split on \n, fallback
initial_project_snapshot.worktree_snapshots[].worktree_path; branch from
git_state.current_branch or archived_git_worktrees.branch_name through
thread_archived_worktrees.
There are no per-message timestamps, only the thread's updated_at and
created_at and initial_project_snapshot.timestamp. Git branch is
initial_project_snapshot.worktree_snapshots[].git_state.current_branch,
captured at thread start.
Other stores¶
SQLite journals and compression¶
db.sqlite: PRAGMA journal_mode=WAL; synchronous=NORMAL
(crates/db/src/db.rs:129-133;
sqlez/src/thread_safe_connection.rs:61-62),
so collect the -wal sidecar. threads.db is opened with plain Connection::open_file and no
journal pragma
(sqlez/src/connection.rs:65),
so it uses the default rollback journal and a threads.db-journal may exist mid-write. Blobs are
zstd::encode_all(json, 3) / decode_all
(db.rs:178-184,535,660):
standard zstd frames, no dictionary, content size present. In Python,
zstandard.ZstdDecompressor().decompress(blob), falling back to stream_reader if the size header
is missing. Rows with data_type='json' are raw UTF-8. No encryption.
Other db.sqlite tables¶
Other per-user stores at
9cd14da,
all in the same db/0-<channel>/db.sqlite, since every sqlez domain (Zed's name for one group of
tables with its migrations) opens that file
(crates/db/src/db.rs:138,
166;
crates/db/src/kvp.rs:20-41;
crates/agent_ui/src/thread_metadata_store.rs:1469;
crates/workspace/src/persistence.rs:986-991,
1078).
They are the tables trusted_worktrees, sidebar_terminal_threads and archived_git_worktrees
and the key-value table scoped_kv_store, whose namespaces include agent_draft_prompts and
created_git_worktrees. The parser reads every one of them, each as a primary source, as
Past-use rows and secondary sources describes.
| Source | Table and key | What it holds | Survives | Joins on | Plan |
|---|---|---|---|---|---|
| Draft prompts | scoped_kv_store(namespace, key, value), namespace agent_draft_prompts, key the sidebar thread_id (kvp.rs:31-36; agent_ui/src/draft_prompt_store.rs:25-58) |
the unsent prompt of a draft thread, a JSON array of ACP ContentBlocks, internally tagged by type (text with text, image, audio, resource_link with name and uri, resource with an embedded resource) (agent-client-protocol-schema/src/v2/content.rs:84-111, schema 1.12.0, the version in Cargo.lock at 9cd14da); the key is the thread_id UUID in hyphenated form (thread_metadata_store.rs:41-44); a draft is a sidebar_threads row with session_id NULL (draft_prompt_store.rs:1-5) |
nothing: deleted with the sidebar row (thread_metadata_store.rs:1141-1164) |
key to sidebar_threads.thread_id |
primary, read: text no other store holds |
| Zed-created worktrees | scoped_kv_store namespace created_git_worktrees, key <host> newline <worktree path> (local for this machine), value {created_at_seconds, created_at_subsec_nanos} (git_ui_core/src/created_worktrees.rs:34-73) |
each linked git worktree Zed made for a thread, with its creation time | the thread's deletion; removed only by forget_created_worktree (created_worktrees.rs:142) |
worktree path to sidebar_threads.folder_paths |
primary, read: the scratch marker |
| Archived worktrees | archived_git_worktrees, thread_archived_worktrees (Record schema) |
for a thread archived from a Zed-created worktree: the worktree and main repository paths, the branch, and the staged, unstaged and original commit ids; the worktree directory is removed and the WIP commits stay in the main repository under refs/archived-worktrees/<id> (agent_ui/src/thread_worktree_archive.rs:77-79, 499-607) |
the worktree's removal; dropped with the ref when the last thread linked to it is deleted (thread_worktree_archive.rs:823-912) |
thread_archived_worktrees.thread_id to sidebar_threads.thread_id |
primary, read |
| Sidebar terminals | sidebar_terminal_threads(terminal_id, title, custom_title, created_at, working_directory, folder_paths, folder_paths_order, main_worktree_paths, main_worktree_paths_order, remote_connection) (agent_ui/src/terminal_thread_metadata_store.rs:483-500) |
a terminal tab opened in the threads sidebar: title, start time (RFC 3339, terminal_thread_metadata_store.rs:519) and working directory, no output |
not determined | folder_paths |
primary, read, metadata only |
| Native sidebar rows | sidebar_threads rows with agent_id NULL (Record schema) |
title, title_override, archived, interacted_at and main_worktree_paths of a native thread |
deleted with the thread | session_id to threads.id |
primary, read as thread state: it repeats no message of threads.db and is deleted with the thread, so it is not a secondary source |
The trusted_worktrees(trust_id, absolute_path, user_name, host_name)
table of the same file is permission state, not past use
(Permission settings and project trust).
Permission settings and project trust¶
At
9cd14da
the user's settings.json (and global_settings.json, which parses the same way) holds
agent.tool_permissions.default and, per tool name (terminal, edit_file, fetch,
mcp:<server>:<tool> and so on), tools.<tool>.default and the regex lists always_allow,
always_deny and always_confirm of {pattern, case_sensitive}, with the modes allow, deny
and confirm
(crates/settings_content/src/agent.rs:397-412,
1067-1140;
assets/settings/default.json:1235-1275),
and agent.sandbox_permissions holds allow_all_hosts, network_hosts[], allow_fs_write_all,
allow_unsandboxed and write_paths[] (a path or {requested, resolved})
(agent.rs:1013-1065).
The same keys can also sit in a user file's release-channel blocks (dev, nightly, preview,
stable), its platform blocks (macos, linux, windows) and each profiles.<name>.settings,
each a whole settings object of its own
(crates/settings_content/src/settings_content.rs:459-469,
501-514).
A project's .zed/settings.json cannot hold them: it is parsed as ProjectSettingsContent, which
has no agent, agent_servers or session key
(crates/settings_content/src/project.rs:44;
crates/settings/src/settings_store.rs:1104-1113).
Zed writes these itself: an "Always allow" or "Always deny" choice on a tool prompt (option ids
always_allow:<tool>, always_deny:<tool>, always_allow_mcp:<tool>, always_deny_mcp:<tool>)
appends the command's sub-patterns to always_allow or always_deny, or sets the tool's default
(crates/agent/src/thread.rs:6899-7005),
and "Allow always" on a sandbox escalation writes sandbox_permissions
(thread.rs:6368-6440,
6658-6673).
The older boolean agent.always_allow_tool_actions is migrated to
tool_permissions.default: "allow" and no longer read
(crates/migrator/src/migrations/m_2026_02_04/settings.rs:7).
An external agent's own mode (Claude Agent's permission mode, for one) is
agent_servers.<id>.default_mode
(agent.rs:854-859).
Project trust, which gates a project's settings, its language and MCP servers and its
.agents/skills, is one trusted_worktrees row per trusted path, with user_name and host_name
for a remote host, in db.sqlite, rewritten whole on every change
(crates/workspace/src/persistence.rs:986-991,
2590-2632;
crates/agent/src/agent.rs:1309-1317),
and session.trust_all_worktrees (default false) skips the check
(crates/settings_content/src/project.rs:498-503).
Telemetry log and crash reports¶
Telemetry (telemetry.metrics, default true,
settings_content.rs:610-633)
sends events to /telemetry/events and appends each one to telemetry.log in the logs directory
(Library/Logs/Zed or <data>/logs, recreated empty at every launch,
telemetry.rs:214-223,
284-286,
637-680).
The agent panel's events carry ids, model, status and token counts, not text, for example
Agent Thread Completion
(thread.rs:3039-3049);
the inline assistant's Inline Assistant Rated event, sent on its thumbs buttons, carries the
prompt, the completion and the selected text
(inline_prompt_editor.rs:640-651),
so a telemetry.log from the current launch can hold inline-assistant text, never panel-thread
text.
telemetry.diagnostics (default true) uploads crash minidumps (a process memory snapshot, *.dmp
beside a *.json metadata file in the logs directory) to Sentry, the crash service, and deletes
both once the upload succeeds
(reliability.rs:51-54,
259-295);
no thread file is attached, though the memory image may hold whatever the panel had loaded.
Format versions¶
Blob version "0.3.0" is current
(db.rs:189);
"0.2.0" and "0.1.0" are
SerializedThread {version, summary, updated_at, messages:[{id:int, role, segments:[{type:"text",text}|{type:"thinking",text,signature?}| {type:"RedactedThinking",data}], tool_uses:[{id,name,input}], tool_results:[{tool_use_id,is_error,content,output}], context, creases, is_hidden}], initial_project_snapshot, cumulative_token_usage, request_token_usage:[...], detailed_summary_state, model{provider,model}, profile}
(legacy_thread.rs:23-164);
no version key means LegacySerializedThread with messages[].text
(:166-205).
Upgrade logic
db.rs:195-330.
Shared and exported threads use SharedThread version "1.0.0"
(db.rs:130-141).
At 9cd14da the shapes the parser reads are unchanged
from a846890, with line numbers moved by one or two: the threads DDL
(db.rs:446-487),
blob version 0.3.0
(db.rs:189),
DbThread and DbSandboxGrants
(db.rs:53-122),
the message enums
(thread.rs:202-217,
287-301,
743-759),
LanguageModelToolUse and LanguageModelToolResult
(crates/language_model_core/src/language_model_core.rs:593-602;
crates/language_model_core/src/request.rs:68-76)
and the sidebar_threads migrations
(thread_metadata_store.rs:1372-1465).
sidebar_threads is migrated in place
(thread_metadata_store.rs:1373-1465);
cross-channel import copies rows between 0-<channel> databases
(thread_import.rs).
SharedThread is used only by the agent panel's two debugging actions, CopyThreadToClipboard (the
thread as base64 JSON on the clipboard) and LoadThreadFromClipboard
(agent_ui.rs:332-335;
agent_panel.rs:3792-3826,
3852-3929);
nothing is sent over the network. A thread loaded that way is saved to threads.db under a new UUID
session id
(agent_panel.rs:3894),
its title prefixed 🔗 and with no initial_project_snapshot, profile or sandbox_grants
(db.rs:153-172),
so the prefix marks a thread pasted in from elsewhere rather than run on this host.
Secrets¶
Blob fields sandbox_grants.write_paths/network_hosts, draft_prompt,
Mention.content (inlined file contents), initial_project_snapshot.git_state.diff
(full diff), and remote_connection (host and user) in sidebar_threads. No
API keys were found in thread storage; the settings files that can hold them are in
Credentials.
Parser plan¶
Globs: {Library/Application Support/Zed,.local/share/zed,AppData/Local/Zed}/threads/threads.db*,
.../db/0-*/db.sqlite*, and {.config/zed,AppData/Roaming/Zed}/{settings,global_settings}.json
(with the Flatpak .var/app/dev.zed.Zed*/{data,config}/zed forms). Requires the zstandard
package.
Rows: one per message content item and per tool_results entry; one
system row per sidebar_threads entry with agent_id set (external agent,
no transcript); the system rows of the subsections below.
| Column | Source |
|---|---|
| timestamp_utc | thread updated_at for all messages (approximate); initial_project_snapshot.timestamp for the thread-start row |
| session_id | threads.id |
| project_path | first of folder_paths.split("\n") |
| git_branch | git_state.current_branch |
| turn_type | User.Text/Mention/Image → user; Agent.Text → assistant; Thinking/RedactedThinking → thinking; ToolUse → tool_use; tool_results[*] → tool_result; Resume/Compaction → system |
| model | model.provider/model.model |
| tool_name, tool_use_id | ToolUse.name / tool_name, ToolUse.id / tool_use_id |
| text | text, arguments JSON, result text; Mention.uri for mentions |
The thread start is followed, at its time, by git remote: <url> (system), the
git_state.remote_url of the thread blob's initial_project_snapshot worktree whose
worktree_path is the thread's project, else of the first worktree, as for git_branch
(Record schema; issue 253).
Source-derived; not observed on any real install.
Model and permission rows (issue 96 and
issue 97), all system, come after the
thread-start row and before the first message. The rows added for
issue 196 rest on source at
9cd14da
alone and a fixture.
Permission rows¶
Thread grants: permission setting: <key>=<value> (thread blob, as of updated_at), one
row per profile, per sandbox_grants.write_paths[] entry (a string, or
the object printed as compact JSON), per sandbox_grants.network_hosts[]
entry, and per sandbox_grants boolean (network_any_host,
allow_fs_write_all, unsandboxed, sandbox_fallback) that is true.
The grants live in the thread blob, so session_id is the thread id and
timestamp_utc the thread's updated_at (the blob's last write; the
moment of the grant is not recorded).
Standing rules, from
Permission settings and project trust, source-derived;
not observed on any real install. The parser reads settings.json and global_settings.json in
the config directory as JSON with comments and trailing commas (Zed parses them with
parse_json_with_comments,
settings_content.rs:450-457)
and writes one permission setting: <key>=<value> (from <file>, file mtime) row per leaf of
agent.tool_permissions and agent.sandbox_permissions (each list entry its own row, an
always_allow, always_deny or always_confirm rule {pattern, case_sensitive} as compact
JSON), per agent.always_allow_tool_actions (the legacy boolean, should a file still hold it),
agent_servers.<id>.default_mode and session.trust_all_worktrees, in the top level and in each
override block and profile, whose name prefixes the key (stable.agent.tool_permissions.default),
at the file's modification time with an empty session_id. A project's .zed/settings.json is not
read, since it cannot hold these keys. Each trusted_worktrees row of db.sqlite is
permission setting: trusted_worktrees.<path>=true[ host=<user_name>@<host_name>] (from <file>
trusted_worktrees, file mtime), project_path the path.
Per call, from Tool results of denied calls, source-derived; not
observed on any real install: a failed tool_result (is_error true) whose whole text is
Permission to run tool denied by user is followed by permission decision: denied, and one whose
text is an automatic denial by permission decision: auto_denied reason=<text>, each with the
call's tool_name and tool_use_id. The sandbox-escalation prompt's denial writes the same user
text
(thread.rs:6393,
6398).
An approval, once or always, leaves no record in the thread and has no row; an "Always allow" or
"Always deny" choice shows only as the settings rows it wrote.
Model rows¶
Covered: model: <model.model> provider=<model.provider> once per thread at the
thread-start time, from the blob's model. The blob holds one model
per thread, so a switch inside a thread is not recorded and there is no
change row; no message records a model (Thread blob).
Past-use rows and secondary sources¶
Every store of Other db.sqlite tables is read as a primary source, all
system rows, source-derived; not observed on any real install:
- A draft prompt is
unsent draft: <text>at its sidebar row'supdated_at, with its first folder asproject_pathand nosession_id(a draft has none), or, when the sidebar row is gone,unsent draft: <text> (from <file> agent_draft_prompts, file mtime)at the file's modification time. The text joins the ACP blocks:textas written,resource_linkandresourceas[mention] <uri>,imageandaudioas markers. The thread blob's owndraft_prompt(same shape) isunsent draft: <text> (thread blob, as of updated_at)after the thread's messages. - An archived worktree is
archived worktree: <worktree_path> of <main_repo_path> branch=<b> staged=<hash> unstaged=<hash> original=<hash> ref=refs/archived-worktrees/<id> (from <file> archived_git_worktrees, file mtime), one per thread linked to it with that thread'ssession_id,project_paththe main repository; the archive time is not recorded. - A sidebar terminal is
sidebar terminal: <custom_title or title> cwd=<working_directory>at itscreated_at, with its first folder asproject_pathand nosession_id. - A native
sidebar_threadsrow with asession_idissidebar thread: title=<title> archived=<true|false> interacted_at=<time|-> main_worktree=<path|->at itsupdated_at, with the thread id: the archive flag, the last user send and the main repository of a thread run in a linked worktree, none of whichthreads.dbholds.
None of these repeats a message of threads.db, so the parser has no secondary source.
Sync, relay and origin rows¶
Zed records no sync state; no thread is synced to the account, shared by link or kept on a server as
a copy of threads.db. Thread text does leave the host in one place beyond the model call: rating a
thread. A thumbs-up or thumbs-down on a native Zed Agent thread (the buttons, or the /helpful and
/not-helpful commands in the message box,
completion_provider.rs:205-210)
posts the whole thread, the DbThread that threads.db stores, serialised as JSON, with the
session id, parent session id, rating and organisation id to /client/feedback/agent_thread; the
comment box that follows a thumbs-down posts the thread again with the comment to
/client/feedback/agent_thread_comments
(thread_view.rs:69-123,
125-165;
cloud_api_client.rs:272-297;
cloud_api_types.rs:107-123;
the payload is
agent.rs:3552-3567).
The tooltip says so: "Rating the thread sends all of your current conversation to the Zed team"
(thread_view.rs:7560).
Rating needs a signed-in Zed account
(cloud_api_client.rs:182-194),
happens only on a click, and is offered while the agent.enable_feedback setting is true, its
default
(agent.rs:352-355;
default.json:1294-1295),
unless the organisation's configuration sets is_agent_thread_feedback_enabled false
(thread_view.rs:7650-7663).
External ACP agents' threads cannot be rated: only the native connection implements the
AgentTelemetry hook that supplies the thread
(agent.rs:3450-3452).
Nothing on disk records that a thread was rated: the rating is held in the view's memory
(thread_view.rs:63-66),
DbThread has no field for it
(db.rs:53-89),
and the request bypasses the telemetry queue, so it is not in telemetry.log
(Telemetry log and crash reports). So this is an upload with no
local marker, and no row is proposed; a copy already sent with a rating stays with Zed after a
delete, and nothing local says so.
The Zed-hosted model provider receives the thread id with each request
(thread.rs:4124;
cloud_llm_client.rs:220-228),
which is the model call, not sync. The collaboration server has a SharedThreadId type that nothing
uses
(ids.rs:96-106),
the collaboration protocol has no thread message (crates/proto/proto/ai.proto carries only agent
and context-server commands), and the remote_connection column of sidebar_threads is not a cloud
id (sidebar_threads and archived worktrees). At
9cd14da
the rating upload is still the only one: a grep for share_thread, ShareThread, sync_thread,
upload_thread and cloud_thread finds no other. The agent records no relay and no origin.
Scratch directory rows¶
Source-derived; not observed on any real install. Both
Scratch directories are marked with a system row that names the
directory in its own project_path:
- A thread blob's
sandboxed_terminal_temp_dirisscratch directory: <path> (sandboxed terminal temp directory of thread <id>) (thread blob, as of updated_at), after the grant rows, at the thread'supdated_at(when the directory was made is not recorded) and with nosession_id, so the thread stays counted under its project. - A
created_git_worktreesentry isscratch directory: <worktree path> (Zed-created git worktree[ of <main>][ host=<host>]) (from <file> created_git_worktrees, created_at)at the recorded creation time, with nosession_id;<main>is themain_worktree_pathsof asidebar_threadsrow whosefolder_pathsholds the worktree, else themain_repo_pathof itsarchived_git_worktreesrow, andhostappears for a remote worktree. The thread run in the worktree keeps the worktree as itsproject_path, since the two files are parsed apart, so its rows count under the scratch directory.
The macOS temp directory is outside the home and not collected (Scratch directories); the row is the only trace the analyzer shows.
Deletion rows¶
The agent records none: nothing local records a deletion. What a delete removes and what survives is in What survives a delete.
Bad-line rows¶
Implemented (issue 168). The parser reads
no file line by line; its bad records are whole files, threads rows and items
inside a thread blob. A file that is not SQLite is one parser: not a
SQLite database row, and a SQLite error while reading one parser:
SQLite error: <error> row. A data blob that does not decode to a
JSON object is parser: unparseable record <rowid> (<reason>):
<stored value>, the stored value being a json row's text or a
zstd row's compressed bytes as lowercase hex (a truncated frame can
decompress to nothing), the reason then ending stored zstd blob as
hex.
A messages[] item of no known variant, a Compaction with
neither Summary nor ProviderNative, an Agent content item of no
known variant, a content or legacy segments[] item of no known type,
and a tool_uses[] or tool_results entry that is not an object is
parser: unparseable record <N> (<reason>): <item as JSON>, N the
message's 1-based position in messages[] and the reason naming the
item (unknown message variant, unknown Agent content variant,
content[1], Agent tool_results entry <id> is not an object). Every
such row is system, carries the thread's session_id and
project_path, an empty timestamp_utc and the threads rowid as
source_line, and follows the thread's other rows. An unknown User
content object stays in the prompt text as JSON.
A scoped_kv_store value that does not decode (a draft prompt that is not JSON, a worktree
record without created_at_seconds) is parser: unparseable record <rowid> (<namespace>[ <key>]:
<error type>: <reason>): <stored value> at the end of the file's rows, with an empty
timestamp_utc and the store's rowid as source_line; a settings file that does not decode to a
JSON object is one parser: <file> is not a JSON object row, and an empty one yields nothing.
These are source-derived; not observed on any real install.
Fixtures¶
threads.db (data is the zstd of the JSON below,
data_type='zstd'):
INSERT INTO threads(id,parent_id,folder_paths,folder_paths_order,summary,updated_at,data_type,data,created_at)
VALUES ('6f1c0b2e-1111-4bbb-8ccc-000000000001',NULL,'/home/alice/proj','0','Fix flaky test','2026-03-01T09:01:05.000000000+00:00','zstd',X'<zstd>','2026-03-01T09:00:00.000000000+00:00');
{"version":"0.3.0","title":"Fix flaky test","updated_at":"2026-03-01T09:01:05Z",
"initial_project_snapshot":{"worktree_snapshots":[{"worktree_path":"/home/alice/proj","git_state":{"remote_url":"git@github.com:alice/proj.git","head_sha":"abc123","current_branch":"main","diff":null}}],"timestamp":"2026-03-01T09:00:00Z"},
"model":{"provider":"anthropic","model":"claude-sonnet-4-5"},
"messages":[
{"User":{"id":"9a7d6c5b-2222-4ddd-9eee-000000000002","content":[{"Text":"run the tests"},{"Mention":{"uri":"file:///home/alice/proj/README.md","content":""}}]}},
{"Agent":{"content":[{"Thinking":{"text":"use pytest","signature":null}},{"ToolUse":{"id":"toolu_01","name":"terminal","raw_input":"{\"command\":\"pytest -q\"}","input":{"type":"json","value":{"command":"pytest -q"}},"is_input_complete":true,"thought_signature":null}},{"Text":"All 3 tests pass."}],
"tool_results":{"toolu_01":{"tool_use_id":"toolu_01","tool_name":"terminal","is_error":false,"content":[{"Text":"3 passed"}],"output":null}},"reasoning_details":null}},
"Resume"]}
db.sqlite:
INSERT INTO sidebar_threads(thread_id,session_id,agent_id,title,updated_at,created_at,folder_paths,folder_paths_order,archived)
VALUES (randomblob(16),'6f1c0b2e-1111-4bbb-8ccc-000000000001',NULL,'Fix flaky test','2026-03-01T09:01:05+00:00','2026-03-01T09:00:00+00:00','/home/alice/proj','0',0);
INSERT INTO sidebar_threads(thread_id,session_id,agent_id,title,updated_at,folder_paths,folder_paths_order)
VALUES (randomblob(16),'ext-claude-code-sess-1','claude-code','External thread','2026-03-01T10:00:00+00:00','/home/alice/proj','0');
The other db.sqlite stores, as 9cd14da creates them (a draft, a thread run in a Zed-created
worktree and archived, project trust and a sidebar terminal):
INSERT INTO sidebar_threads(thread_id,session_id,agent_id,title,updated_at,folder_paths,folder_paths_order,archived,main_worktree_paths,interacted_at)
VALUES (X'0b5e2d7a44444ddd9eee000000000004',NULL,NULL,'New Thread','2026-03-01T11:00:00+00:00','/home/alice/proj','0',0,NULL,NULL),
(X'<16 bytes>','6f1c0b2e-3333-4bbb-8ccc-000000000003',NULL,'Fix tests in a worktree','2026-03-01T12:00:00+00:00','/home/alice/worktrees/fix-tests/proj','0',1,'/home/alice/proj','2026-03-01T11:59:00+00:00');
INSERT INTO scoped_kv_store(namespace,key,value) VALUES
('agent_draft_prompts','0b5e2d7a-4444-4ddd-9eee-000000000004','[{"type":"text","text":"refactor the parser"},{"type":"resource_link","name":"main.py","uri":"file:///home/alice/proj/main.py"}]'),
('created_git_worktrees','local' || char(10) || '/home/alice/worktrees/fix-tests/proj','{"created_at_seconds":1772362800,"created_at_subsec_nanos":500000000}');
INSERT INTO trusted_worktrees(absolute_path,user_name,host_name) VALUES
('/home/alice/proj',NULL,NULL), ('/home/alice/remote','alice','build.example');
INSERT INTO archived_git_worktrees VALUES
(1,'/home/alice/worktrees/fix-tests/proj','/home/alice/proj','fix-tests','a1b2c3','d4e5f6','0a0b0c');
INSERT INTO thread_archived_worktrees VALUES (X'<16 bytes>',1);
INSERT INTO sidebar_terminal_threads(terminal_id,title,created_at,working_directory,folder_paths,folder_paths_order)
VALUES ('7c1d0e2f-6666-4fff-8aaa-000000000006','zsh','2026-03-01T10:30:00+00:00','/home/alice/proj','/home/alice/proj','0');
The thread blob's 9cd14da fields: "sandboxed_terminal_temp_dir":
"/private/var/folders/xy/abc/T/zed-agent-terminal-Ab12Cd", "draft_prompt":
[{"type":"text","text":"and then lint"}], and a denied call's result
{"tool_use_id":"toolu_02","tool_name":"terminal","is_error":true,
"content":[{"Text":"Permission to run tool denied by user"}]}.
settings.json (JSON with comments):
{"agent": {"tool_permissions": {"default": "confirm", "tools": {"terminal": {
"always_allow": [{"pattern": "^cargo test"}],
"always_deny": [{"pattern": "^rm -rf", "case_sensitive": false}]}}},
"sandbox_permissions": {"network_hosts": ["*.npmjs.org"], "allow_unsandboxed": false}},
"agent_servers": {"claude-acp": {"type": "registry", "default_mode": "plan"}},
"session": {"trust_all_worktrees": false},
"stable": {"agent": {"tool_permissions": {"default": "allow"}}}}
Source host rows¶
The agent records no source-host integration. A grep of the agent, agent UI and ACP server crates
at 9cd14da
for github, gitlab, bitbucket, oauth, installation, repositor, pull request and
clone finds no connected source-host account, no cached repository list and no
repository-bound remote conversation: the hits are the local repository a thread runs in, context
server (MCP) extension installs and host patterns in sandbox examples. The one GitHub token
Zed holds is the GitHub Copilot model provider's read:user grant, a model credential kept in
the keychain (Credentials), not a grant to act on repositories; a project's own
git remote, which the thread blob's git_state.remote_url records, is not an integration.
Confidence¶
- High: every path from
paths.rsand thedirscrate; the credential provider's behaviour; the DDL, blob and enum shapes, zstd framing, path derivation, WAL settings and version handling, all from source with no real install (Observed). - Medium: the Flatpak
~/.var/app/<id>/{config,data}layout (Flatpak convention; Zed only reads theFLATPAK_XDG_*variables the runtime sets). - Medium: the
MentionUrivariant encoding, and the legacyRolespelling in 0.2.0 blobs. - Not determined: whether any legacy
conversations/data survives on old installs. - Not determined: whether a thread's sidebar rows outlive "Delete all", and whether a sidebar terminal row outlives its thread (What survives a delete).