qwen-code¶
Qwen Code (catalog agent qwen-code), from QwenLM/qwen-code,
is a fork of Gemini CLI (gemini-cli.md), but its transcript
format has fully diverged: Gemini writes a metadata line plus message records
with $set/$patch/$rewindTo operations, Qwen writes a flat,
Claude-Code-style record stream with uuid, parentUuid, cwd and
gitBranch on every line. What it still shares with Gemini CLI is the per-project
tmp/<sha256(cwd)>/ directory, which Gemini CLI has since replaced with slug directories and keeps
only as a legacy copy
(Transcript stores in gemini-cli.md), and in it logs.json,
shell_history and the checkpoint-<tag>.json files, which Qwen still writes in the raw-array
shape Gemini CLI has left behind
(Format versions in gemini-cli.md). Besides the CLI
it has a Web Shell served by qwen serve, Qwen Code Desktop, a Tauri app that wraps that Web
Shell (Qwen Code Desktop), and messaging-platform channels. Paths cited are
relative to the clone. The agent is parsed: the qwen-code parser reads the transcripts,
logs.json, usage_record.jsonl, trustedFolders.json, settings.json and the channel route
files (Parser plan).
Source and evidence¶
QwenLM/qwen-code, commit
2c591ecc08a6fa080342f9b1b9f7f43215178cbb
(2026-10-03, version 0.24.7), Apache-2.0. Open source. All claims are source-level (packages/...
file:line) unless marked docs/ (repo documentation, used for directories whose writer lives in
code not grepped here). The layout has grown far beyond the v1.1 catalog (channels, boards, arena,
workflows, audits).
Observed¶
- No real install.
~/.qwendoes not exist on this workstation, so no live check. The channel route files are fixture only: no real install has shown either file (Channel routes).
Source reviews¶
- 2026-10-21: QwenLM/qwen-code at
5dcf7c7(0.25.3): the per-call decision, approval-mode,settings.jsonpermission andusage_record.jsonlrows moved from proposed to parsed, theui_telemetrytool-call event name was corrected toqwen-code.tool_call, the session registry gained its secret glob, and the source-host review of the GitHub and GitLab channels and the pull-request sidecar was added (Source host rows). - 2026-10-19: HEAD
5dcf7c7(2026-10-20 UTC+8, version 0.25.3) against the commit above;storage.ts,paths.tsand the channel runtime had not changed. It addedusage_record.jsonl, the managed auto-memory, the scratch workspaces and worktrees, the delete path, Qwen Code Desktop, the per-call and standing permission records,executionContextand the record subtypes of Format versions, and the reading oftrustedFolders.jsonas JSONC (JSON with comments, trailing commas and a byte-order mark allowed).
Collection¶
Per-user storage¶
QWEN_DIR = '.qwen'
(core/src/utils/paths.ts:14).
getGlobalQwenDir() returns $QWEN_HOME (with ~ expansion) else os.homedir()/.qwen
(core/src/config/storage.ts:193-203).
No platform branch for user paths; platformFoldsCase() only affects comparisons
(30-32).
Same layout on Linux, macOS, %USERPROFILE%\.qwen on Windows; no XDG.
A second root, the runtime base dir, defaults to the global dir but is relocated by
$QWEN_RUNTIME_DIR
(storage.ts:172-191).
It holds tmp/, projects/, debug/
(229-238)
and resources/<sessionId>
(core/src/utils/sessionStorageUtils.ts:813).
Global dir files (all storage.ts unless noted): settings.json
(209),
installation_id
(213),
google_accounts.json
(217,
defined, no writer found), commands/
(221),
memory.md
(225,
no callers found; docs use ~/.qwen/QWEN.md,
docs/users/features/memory.md:30),
ide/
(241-245),
plans/
(325),
bin/
(340),
arena/
(344;
core/src/agents/arena/ArenaManager.ts:136),
audits/<sha256>/
(371),
workflows/
(670),
extensions/
(768),
skills/ plus ~/.agents/skills
(17,
758),
rules/
(core/src/config/rulesDiscovery.ts:331),
sessions/<pid>.json live session registry, one record per running session
(Session registry),
(core/src/services/session-registry.ts:322,332),
usage_record.jsonl, one usage summary per session that outlives the session
(usage_record.jsonl;
core/src/services/usageHistoryService.ts:140),
model-registry.json
(core/src/models/model-catalog.ts:64),
artifacts/
(core/src/tools/artifact/local-publisher.ts:30),
file-history/ edit backups
(core/src/services/sessionService.ts:3317,4274),
boards/
(core/src/agents/team/board-lock.ts:32-35),
batch/
(cli/src/commands/batch-task.ts:283),
tip_history.json
(cli/src/services/tips/tipHistory.ts:132),
startup-perf/
(cli/src/utils/startupProfiler.ts:339),
source.json
(core/src/telemetry/qwen-logger/qwen-logger.ts:372-376),
channels/{service.pid,sessions.json,cron.json}
(cli/src/commands/channel/pidfile.ts:55,
runtime.ts:44,89),
updates/npm/
(cli/src/utils/managed-npm-update.ts:257),
scratch-workspaces/
(cli/src/serve/run-qwen-serve.ts:5231),
extension-store/
(core/src/extension/extension-store.ts:523),
debug/<sessionId>.txt
(233-238),
trustedFolders.json
(cli/src/config/trustedFolders.ts:33-41,
override QWEN_CODE_TRUSTED_FOLDERS_PATH), .env
(cli/src/config/environment.ts:192-195).
Named in the docs only, with no writer found in the code at the first commit: desktop-relay/,
computer-use/, browser-use/, todos/, paste-cache/, output-styles/, output-language.md,
log/otel-*, locales/ (user locale overrides; the bundled locales is in the install dir,
cli/src/i18n/index.ts:47).
projects/<sanitizeCwd(cwd)>/
(619-623;
non-alphanumerics replaced by -,
paths.ts:388-391):
chats/<uuid>.jsonl
(sessionService.ts:459,948;
chatRecordingService.ts:1305),
chats/<id>.worktree.json
(cli/src/serve/routes/session-pr-backfill.ts:314),
workflows/ run snapshots and generated/ scripts
(679-723),
memory/, the managed auto-memory (below).
tmp/<sha256(cwd)>/
(625-629;
paths.ts:368-372):
logs.json, checkpoint-<tag>.json
(core/src/core/logger.ts:14,204,693),
checkpoints/, tool-results/
(633),
shell_history
(773),
attachments/, scheduled_tasks.json, otel/ (docs). No shadow git repository exists in this fork
(gitService.ts absent).
The memories/ and memory/ directories, named in the docs only at the first commit, are written
by code at
5dcf7c7:
projects/<sanitized project>/memory/ holds the managed auto-memory, on by default, and
memories/ the user-level form
(core/src/memory/paths.ts:19,
34,
116-148;
documents in Managed auto-memory).
Scratch directories¶
~/.qwen/scratch-workspaces/ is the managed scratch root qwen serve (the daemon behind the Web
Shell and the desktop app) prepares for a session that has no workspace
(cli/src/serve/run-qwen-serve.ts:5285-5292).
It is excluded (Exclusions), so the files a session made there are not collected,
while the transcript that names it is. The per-project worktrees,
<project>/.qwen/worktrees/<slug>, are in Project-local files; how the
transcripts record both is in Scratch workspaces and worktrees.
Qwen Code Desktop¶
At
5dcf7c7,
Qwen Code Desktop (packages/desktop), a Tauri app that wraps the Web
Shell served by qwen serve, has the identifier com.alibaba.qwen-code and keeps
desktop-state.json (window and zoom state) in its own config directory
(desktop/src-tauri/tauri.conf.json:3-5;
desktop/src-tauri/src/desktop_state.rs:177-182);
its sessions are ordinary ~/.qwen transcripts. That directory and the webview's own storage are
not in the catalog; their platform paths were not determined from source. Its Local Control setting
shares the live daemon with a phone on the same network
(desktop/README.md:17);
whether the app or that setting leaves a local record of a paired device was not determined.
Credentials¶
oauth_creds.jsonplaintext, mode 0600, plusoauth_creds.lock(core/src/qwen/sharedTokenManager.ts:26-27,643;qwenOAuth2.ts:41,1094). No OS keychain for the main login.mcp-oauth-tokens-v2.jsonandextension-secrets-v1.json: AES-GCM withscryptSync('qwen-code-oauth', '<hostname>-<username>-qwen-code')(core/src/mcp/token-storage/file-token-storage.ts:30-38); decryptable given hostname and username. Legacymcp-oauth-tokens.jsonpath still defined (storage.ts:205-206).extensions/<name>/.qwen-extension-git-credentials.jsonis a selector file; the secret goes to the keyring serviceQwen Code Extension Git Credentials(core/src/extension/extension-git-credentials.ts:19-22,66-67,152).settings.jsonmodelProviders[].apiKeyandenvKey(core/src/models/modelsConfig.ts:725,926);.envin~/.qwen/.envor~/.env(environment.ts:192-195,224-239). Channel credentials underchannels/: the QQ bot adapter writesappIdandappSecrettochannels/<channel name>-credentials.json(channels/qqbot/src/accounts.ts:13-15,44-52), and the WeChat adapter keeps itstokeninchannels/weixin/account.json(or underWEIXIN_STATE_DIR,channels/weixin/src/accounts.ts:21-40); the secret globs.qwen/channels/*-credentials.jsonand.qwen/channels/weixin/account.jsonmatch those two. The other routing, cursor, pairing and history files underchannels/hold no credential:sessions.jsonanddaemon/<hash>/routes.json(the routing tables a parser reads,cli/src/commands/channel/runtime.ts:43-60),cron.json, the email adapter's per-workspacestate.json(IMAP positions and reply routes, no password,channels/email/src/state.ts:29-36) and the polling adapters'<name>-<hash>-poll-cursor.json(channels/base/src/PollingChannelBase.ts:128-135). The~/.qwen/channels/dingtalk-groups.jsonnamed in a design document is a proposed store of group conversation ids, not a credential, and no writer exists at this commit. Platform bot tokens set insettings.jsonare covered by that file's glob; among them the GitHub and GitLab channels' personal access tokens, thetokenfield of achannelsentry (channels/github/src/index.ts:13-20,channels/gitlab/src/index.ts:14-21), which the GitHub channel may replace with the token of the host's ownghlogin (channels/github/src/GithubAdapter.ts:76-90; Source host rows).sessions/<pid>.json, the session registry (Session registry): itsipcTokenadmits a connection to a running session's socket, so the secret glob.qwen/sessions/*.jsonflags every record.
Exclusions¶
.qwen/updates, .qwen/extension-store, .qwen/bin,
.qwen/scratch-workspaces, .qwen/extensions/*/node_modules, .qwen/arena
(parallel agent working copies), .qwen/artifacts (published build
outputs), .qwen/resources, .qwen/startup-perf, .qwen/locales (user locale overrides) and
.qwen/sandbox (no writer found at either commit reviewed). .qwen/worktrees, the agent's git
worktrees inside a project (Scratch workspaces and worktrees),
is excluded as a copy of the repository. Keep file-history/
(pre-edit file copies: evidence), audits/, debug/, boards/.
Project-local files¶
QWEN.md
(core/src/utils/memory-constants.ts:7),
.qwenignore
(core/src/utils/qwenIgnoreParser.ts:13),
.qwen/settings.json
(648),
.qwen/commands
(652),
.qwen/workflows
(661),
.qwen/extensions/qwen-extension.json
(746-750),
.qwen/skills and .agents/skills
(17),
.qwen/batch
(cli/src/commands/batch-workflow.ts:486).
No per-project database.
At
5dcf7c7,
.qwen/settings.json in a project also holds the "always allow" rules the user saved for that
project, under permissions.allow
(Permission lists in settings.json).
.qwen/worktrees/<slug> is a scratch directory: a git worktree the enter_worktree tool creates
(core/src/tools/enter-worktree.ts:29).
No exclusion covers it, so the qwen-code|.qwen project entry collects each worktree checkout in
full. .qwen/memory/ holds the auto-memory when QWEN_CODE_MEMORY_LOCAL=1 is set
(core/src/memory/paths.ts:116-148).
Where the project path is recorded¶
projects/<sanitized>/chats/*.jsonl: first record hascwd(sessionService.ts:154,2757,2835,3975). The directory name is lossy (/and.both become-), so the record is the source.trustedFolders.json: keys are folder paths.tmp/<sha256>is one-way; match it by hashing discovered paths.sessions/<pid>.json:cwdbesidesessionId, for a session that is running or that exited without removing its record (Session registry).usage_record.jsonl:projectbesidesessionId(core/src/services/usageHistoryService.ts:37-43), kept after the session's deletion (usage_record.jsonl).chats/<sessionId>.worktree.json:originalCwd, the project, andworktreePath, the scratch worktree inside it (Scratch workspaces and worktrees).
Parsing¶
Transcript stores¶
Base directory ~/.qwen, overridable by $QWEN_HOME
(core/src/config/storage.ts:193-202)
or the runtimeOutputDir setting
(128-130).
- Session:
~/.qwen/projects/<sanitizeCwd(cwd)>/chats/<sessionId>.jsonl, one file per session (storage.ts:619-623;utils/paths.ts:388-392: every character outside[a-zA-Z0-9]becomes-, Windows lower-cased;services/chatRecordingService.ts:1305,1318-1321).sessionIdis a UUID; listing accepts/^[0-9a-fA-F-]{32,36}\.jsonl$/(services/sessionService.ts:459). Archived sessions move tochats/archive/(sessionService.ts:949-951). - Sidecars in the same directory:
<sessionId>.runtime.json(storage.ts:733-740; snake_case keys,startedAtepoch seconds,utils/runtimeStatus.ts:46-60),<sessionId>.worktree.json(worktreeSessionService.ts:38),<sessionId>.ledger.jsonlprompt ledger (sessionService.ts:1043-1046). ~/.qwen/tmp/<sha256(cwd)>/:logs.json,checkpoint-<tag>.json,checkpoints/,shell_history,tool-results/(storage.ts:625-633,742-744,772-774;core/logger.ts:14,203-204,693;paths.ts:368-373).- Managed-engine sessions: same
.jsonllocation plus~/.qwen/resources/<sessionId>/(utils/sessionStorageUtils.ts:806-816). - Channel routes:
~/.qwen/channels/sessions.jsonand the daemon's~/.qwen/channels/daemon/<workspace hash>/routes.json; see Channel routes. - Past use beyond the transcript: see Other stores.
What survives a delete¶
At
5dcf7c7,
removing a session first salvages its usage into ~/.qwen/usage_record.jsonl
(sessionService.ts:3347-3358,
3440-3452),
then deletes the transcript, its worktree and pull-request sidecars, its prompt ledger, its
file-history backups and its managed resources directory resources/<sessionId>
(sessionService.ts:3539-3558);
archiving moves a session to chats/archive/ instead. Unlike
Gemini CLI there is no age-based retention:
sessionRetention and maxAge occur in no session code. The per-project logs.json under
~/.qwen/tmp/<sha256(cwd)>/, which holds the user's prompts and model switches, is not touched, and
neither are shell_history, the /chat save checkpoints (checkpoint-<tag>.json) or the managed
auto-memory documents, which carry no session id or time. The tool checkpoints in checkpoints/
are gone after the next start (shell_history and checkpoints).
usage_record.jsonl keeps a deleted session's summary but no text
(usage_record.jsonl). A session driven from a messaging platform also lives
on that platform, and the daemon's routes.json stays on disk while the foreground service's
channels/sessions.json is deleted on a clean shutdown. No deletion marker is written: the
surviving usage_record.jsonl entry and the logs.json prompts are all that show a removed
session. The analyzer reads the transcripts in chats/ and chats/archive/, logs.json and
usage_record.jsonl as secondary sources and both channel route files, and not shell_history,
the checkpoints or the memory documents (see
Secondary sources).
Scratch workspaces and worktrees¶
~/.qwen/scratch-workspaces/(Scratch directories): a transcript whosecwdis under it lies under the home catalog entry.qwen, so the analyzer's catalog rule lists it underscratch_directories.<project>/.qwen/worktrees/<slug>(Project-local files): recorded inchats/<sessionId>.worktree.jsonas{slug, worktreePath, worktreeBranch, originalCwd, workspaceCwd?, originalBranch, originalHeadCommit, supersededBy?, supersedes?}(core/src/services/worktreeSessionService.ts:38-80). Records written inside it carry the worktree ascwd. No rule classes it as scratch.
Record schema¶
ChatRecord¶
Every line is a ChatRecord
(services/chatRecordingService.ts:336-521):
uuid, parentUuid: string|null, sessionId, timestamp (ISO 8601,
1490),
type: 'user'|'assistant'|'tool_result'|'system', subtype? (36 values,
354-389,
including chat_compression, slash_command, custom_title, session_model, rewind,
turn_result, branch_checkpoint, managed_session_header_v1), provenance?
(270-276),
cwd, version (CLI version,
1501),
gitBranch?
(1502-1510
via getGitBranch(cwd)), promptId?, daemonPromptId?, message?: Content ({role, parts}),
usageMetadata?, model?, contextWindowSize?,
toolCallResult?: Partial<ToolCallResponseInfo> & {status?}, systemPayload?, agentId?,
agentName?, isSidechain?, agentRunId?, agentRound?, forkedFrom?: {sessionId, messageUuid}.
Base fields are set at
1474-1504;
parentUuid is the previous record's uuid.
- user:
message = {role:'user', parts:[{text}]}, optionalsystemPayload: UserPromptRecordPayload {displayText, hookContext, attachmentReferences?, resourceLinks?}(542-557),promptId(2281-2304). - assistant:
model,message = {role:'model', parts}whose parts carrytext,thought: truetext, andfunctionCall {id, name, args};usageMetadata,contextWindowSize(2575-2611). - tool_result:
message = {role:'user', parts:[{functionResponse:{id, name, response}}]}plustoolCallResult {callId, responseParts, resultDisplay, error, errorType, executionStatus, contentLength, status}(core/turn.ts:219-246; status valuescore/coreToolScheduler.ts:710, the same seven strings as Gemini CLI'sCoreToolCallStatus, Record schema ingemini-cli.md) (2741-2840). - system payloads of interest:
ChatCompressionRecordPayload {info, compressedHistory: Content[], promptIds?}(610-621),SlashCommandRecordPayload {phase, rawCommand, sentToModel?}(628-644),SessionModelRecordPayload {modelId, authType, baseUrl?}(703-708),TurnResultRecordPayload {promptId, state, startedAt?, endedAt, promptText?, resultText?}(949-965),RewindRecordPayload {truncatedCount}(837-839).
logs.json and checkpoints¶
logs.json adds type: 'model_switch' with message = JSON
ModelSwitchEvent {fromModel, toModel, reason, context?}
(logger.ts:16-33).
checkpoint-<tag>.json is a raw Content[] array
(logger.ts:726),
the shape Gemini CLI wrote before it moved to {history, authType}
(Format versions in gemini-cli.md).
Permission records¶
These records are present at
5dcf7c7.
CRS below is
core/src/services/chatRecordingService.ts
at that commit.
Per-call permission. A system record with subtype ui_telemetry holds systemPayload.uiEvent
(CRS 3124-3140;
written for every telemetry event,
core/src/telemetry/loggers.ts:172-175).
For a tool call the event has "event.name": "qwen-code.tool_call" (logToolCall replaces the
event's own tool_call with the EVENT_TOOL_CALL constant before recording it,
core/src/telemetry/loggers.ts:317-331;
core/src/telemetry/constants.ts:11),
call_id, function_name, function_args, status (success, error, cancelled) and
decision, set only when the user was asked: accept (allow once), auto_accept (any "allow
always" choice), modify (edited first) or reject
(core/src/telemetry/types.ts:182-245;
core/src/telemetry/tool-call-decision.ts:9-36).
Approval mode. Every user prompt record, and every mid_turn_user_message, carries
executionContext: {modelId, authType?, approvalMode}
(CRS 475-479,
2329-2335,
2356-2359,
2420-2422),
approvalMode one of plan, default, auto-edit, auto, yolo
(core/src/config/approval-mode.ts:7-13).
auto, the AUTO mode in which a model classifier decides each call, is not among Gemini CLI's
approval modes (Other stores in gemini-cli.md).
A daemon session also writes a session_approval_mode record,
systemPayload {mode, prePlanMode?, planExecutionMode?}, whenever the mode changes
(CRS 749-754,
3516-3545).
Joins and timestamps¶
Session = file = sessionId on every record; cwd and gitBranch on every record (the lister
reads the first record,
sessionService.ts:1184-1197).
Tool call message.parts[].functionCall.id joins tool_result.toolCallResult.callId and
message.parts[].functionResponse.id. Turns group by promptId, the tree by parentUuid,
subagents by isSidechain and agentId, forks by forkedFrom.
Other stores¶
No SQLite or binary store; all stores are JSON or JSONL text.
The past-use stores beside the transcript are described at
5dcf7c7;
of them logs.json and usage_record.jsonl are parsed.
logs.json¶
logs.json (secondary, parsed): user prompts and model switches. The session delete does not touch
it (logs.json and getProjectTempDir do not occur in
core/src/services/sessionService.ts).
usage_record.jsonl¶
~/.qwen/usage_record.jsonl (secondary, parsed): one JSON object per session, {version: 1,
sessionId, timestamp, startTime, project, durationMs, totalLatencyMs?, models: {<model id>:
{requests, inputTokens, outputTokens, cachedTokens, thoughtsTokens, totalTokens}}, tools:
{totalCalls, totalSuccess, totalFail, byName: {<tool>: {count, success, fail}}}, files: {linesAdded,
linesRemoved}, skills?}, with epoch-millisecond times, last record per sessionId winning
(core/src/services/usageHistoryService.ts:37-77,
140).
A record is appended when a session exits with model activity and at /clear, with project the
project root
(cli/src/llm.tsx:1200-1216,
cli/src/ui/commands/clearCommand.ts:80-98),
and a session's usage is salvaged into this file before its transcript is deleted, with project
the first record's cwd
(core/src/services/usageHistoryService.ts:239-281)
(What survives a delete), so a deleted session survives here as its id,
project, start and duration, the models it used and how often it called each tool. Joins on
sessionId.
Managed auto-memory¶
~/.qwen/projects/<sanitized project>/memory/ (a projection, not parsed): the managed auto-memory
documents a background agent extracts from conversations, on by default
(memory.enableManagedAutoMemory and memory.enableManagedAutoDream,
cli/src/config/settingsSchema.ts:2457-2476;
root at
core/src/memory/paths.ts:116-148,
or <project>/.qwen/memory with QWEN_CODE_MEMORY_LOCAL=1). No session id or time; outlives the
sessions.
shell_history and checkpoints¶
shell_history, checkpoint-<tag>.json and checkpoints/ hold what they hold in Gemini CLI
(Other stores in gemini-cli.md): the shell-mode commands with no
time or session id, the /chat save histories and the tool checkpoints (shell history at
core/src/config/storage.ts:772-774);
checkpoints/ is deleted at start unless an execution sandbox is configured
(cli/src/utils/cleanup.ts:126-136;
cli/src/llm.tsx:587).
Channel routes¶
~/.qwen/channels/sessions.json, written by qwen channel start, which drives sessions from a
messaging platform (Telegram, WeChat, QQ, DingTalk, WeCom, Feishu, GitHub, GitLab or email; the
conversation then also lives on that platform's servers)
(cli/src/commands/channel/runtime.ts:43-45).
It is one JSON object keyed by routing key (<channelName>:<chatId> and similar forms by scope),
each value {sessionId, target: {channelName, messageRoute?, senderId, chatId, threadId?, isGroup?},
cwd, turns?, startedAt?, isolation?, workspaceCwd?}, startedAt epoch milliseconds
(channels/base/src/SessionRouter.ts:25-36,
1559-1593;
channels/base/src/types.ts:180-187).
It is rewritten on every routing change and deleted on a clean shutdown (clearAll,
SessionRouter.ts:1461-1469;
cli/src/commands/channel/start.ts:158),
so it survives only a running service or a crash. The daemon-managed form writes the same shape to
~/.qwen/channels/daemon/<workspace hash>/routes.json
(runtime.ts:47-62;
cli/src/commands/channel/daemon-worker.ts:656-662)
and only disposes the router on shutdown
(877,
892),
so that file stays. The session transcripts themselves are ordinary chats/<sessionId>.jsonl files.
Session registry¶
~/.qwen/sessions/<pid>.json (not parsed): an index of the sessions running now. Each top-level
session writes its record at start and unlinks it on exit; a process that hosts several sessions
(the qwen --acp child a daemon spawns) writes one <pid>-<8 hex>.json per session
(core/src/services/session-registry.ts:7-20,
321-333,
486-491,
624-653).
A record left by a process that died is removed by the next listing that finds its process gone
(741-786).
The record, written with mode 0600, is {schemaVersion, pid, procStart, pidNs, sessionId, cwd, name,
startedAt, qwenVersion, kind?, ipcPath?, ipcToken?}, startedAt epoch milliseconds, kind the
registering surface (tui when absent), ipcPath the session's peer-messaging socket and
ipcToken the token a connection to it must present
(104,
186-236).
Joins on sessionId.
Permission lists in settings.json¶
At
5dcf7c7,
settings.json (~/.qwen/ and <project>/.qwen/) holds
permissions.allow, permissions.ask and permissions.deny rule lists and permissions.autoMode
(the AUTO classifier's hints and limits)
(cli/src/config/settingsSchema.ts:2691-2740),
and tools.approvalMode, whose default is auto
(3286-3300).
An "always allow" choice for the project or for the user appends its rules to permissions.allow of
the matching file
(core/src/core/permission-helpers.ts:194-243;
cli/src/config/config.ts:2624-2636).
The file is read with comments stripped and a byte-order mark removed
(cli/src/config/settings.ts:921). Qwen Code has no auto-saved.toml policy file, where Gemini CLI keeps its saved grants
(Other stores in gemini-cli.md).
Format versions¶
version on every record is the CLI version. Managed-engine transcripts begin with
subtype:'managed_session_header_v1'
({formatVersion, minimumReader, sessionKey, engine:'managed', ...},
managed-runtime/managed-session-records.ts:220-229)
followed by managed_session_event_v1 and commit_v1 records
(209-241);
detector
sessionStorageUtils.ts:827,920-958.
logs.json and checkpoint-*.json keep the formats inherited from Gemini CLI
(logs.json and checkpoints).
Between the first commit and
5dcf7c7
(version 0.25.3) the paths did not change and the records did (CRS as in
Permission records); none of this has been observed on a real install:
- Record subtypes.
notificationandcronrecords (type: "user",provenance: "system", CRS 2655-2690) and the newagent_mentionandagent_messagerecords (type: "user",provenance: "external_agent"unless a user mention, withagentIdandagentName; messages from another agent of the same workspace, CRS 2541-2585) are parsed assystemrows (issue 172):<subtype> (provenance=<provenance> agent=<agentName> agentId=<agentId>): <text>, the agent from the record'sagentIdandagentNameor, on an agent'sagent_mention, fromsystemPayload.author(agentId,name), parts not recorded left out,<text>the message parts orsystemPayload.displayText. Atype: "user"record is auserrow only when itsprovenanceis absent orreal_userand its subtype is notnotification,cronoragent_message, so the user's ownagent_mention(provenance: "real_user") stays auserrow and agoal_runtimerecord (provenance: "goal_runtime") becomes asystemrow too. Newturn_attemptrecords andturn_result.cancelReason(CRS 998, 3713-3734) fall to the genericsystemrow. A tool result may carry subtypecode_mode_tool_result(CRS 403); the parser reads it as an ordinarytool_result. trustedFolders.json: Qwen now reads the file withparseJsoncObject, which allows trailing commas and strips a byte-order mark (cli/src/utils/jsonc-editor.ts:24, 92-107;cli/src/config/trustedFolders.ts:172-182); how the parser reads it is in Permission rows.
Secrets¶
~/.qwen/oauth_creds.json
(storage.ts:16;
qwen/qwenOAuth2.ts:41),
mcp-oauth-tokens.json
(206),
.qwen-extension-git-credentials.json
(extension/extension-git-credentials.ts:20).
session_model.systemPayload.baseUrl is an endpoint, not a key.
tool_result.toolCallResult.responseParts and message.parts[].functionResponse.response hold raw
tool output; UserPromptRecordPayload.hookContext is hook stdout. ipcToken in a
sessions/<pid>.json record (Session registry) is the token that admits a
connection to the running session's socket
(services/session-registry.ts:235).
Parser plan¶
Globs (home-relative): .qwen/projects/*/chats/*.jsonl,
.qwen/projects/*/chats/archive/*.jsonl, .qwen/tmp/*/logs.json, .qwen/trustedFolders.json,
.qwen/settings.json (also a project's), .qwen/usage_record.jsonl and the channel route files.
Exclude
*.runtime.json, *.worktree.json and *.ledger.jsonl from transcript
parsing.
Rows: one per record; an assistant record additionally yields a tool_use
row per functionCall part and a thinking row per thought:true part.
| Column | Source |
|---|---|
| timestamp_utc | timestamp |
| session_id | sessionId |
| project_path | cwd |
| git_branch | gitBranch |
| turn_type | user/assistant/tool_result as-is, except a user record the user did not type (provenance other than real_user, or subtype notification, cron or agent_message) → system; system → system; functionCall → tool_use; thought part → thinking |
| model | model |
| tool_name | functionCall.name / functionResponse.name |
| tool_use_id | functionCall.id / toolCallResult.callId |
| text | text parts, systemPayload.displayText, rawCommand, toolCallResult.status plus errorType |
Permission rows¶
Permission rows (issue 96 and issue 97), added with the model rows in Model:
permission decision: <status>(system, the result'stool_nameandtool_use_id) after atool_resultrow whosetoolCallResult.statusiscancelledorawaiting_approval; the[<status>]prefix on the result row stays.permission setting: trustedFolders.<path>=<value> (from <file>, file mtime)(system,project_paththe folder, no session id, the file's modification time, line 1), one per key of~/.qwen/trustedFolders.json. The file is a JSON object read with comments stripped (cli/src/config/trustedFolders.ts:162) and rewritten preserving them (259); keys are folder paths and values aTrustLevel:TRUST_FOLDER,TRUST_PARENTorDO_NOT_TRUST(44-48, 60-61). Qwen now also accepts trailing commas and a byte-order mark (Format versions), and the parser reads the file through thetrusted_folder_rowsreader it shares with thegemini-cliparser, which strips a byte-order mark, comments and trailing commas outside strings. The value is printed as recorded. A file that is still not a JSON object is oneparser: <file> is not a JSON objectrow.
The following rows are source-derived; not observed on any real install (shapes in Permission records and Permission lists in settings.json):
- Per call: a
ui_telemetryrecord keeps itsui_telemetry: <JSON>row; when itsuiEventhasevent.nameqwen-code.tool_callortool_calland adecision, it is followed bypermission decision: <decision> (tool_call ui_telemetry)withtool_namethe event'sfunction_nameandtool_use_iditscall_id, which join the call'stool_userow. Both spellings are matched:logToolCallrecordsqwen-code.tool_call(core/src/telemetry/loggers.ts:317-331), while theToolCallEventclass's own default is the baretool_call(core/src/telemetry/types.ts:214), which an older build that recorded the event without the rename may have written. The record is written when the call completes, so the row follows the result. No separate record of the AUTO classifier's verdict was found (classifieranddenialdo not occur inCRS); a call it blocks shows only as the tool result. - Approval mode:
permission mode: <approvalMode>at the first record whoseexecutionContextnames one andpermission mode: <old> -> <new>on each change, tracked per session, before the record's own rows. Asession_approval_moderecord ispermission mode: <mode>orpermission mode: <old> -> <mode>in the same tracking, followed byprePlanMode=<mode>andplanExecutionMode=<mode>when recorded. - Standing rules: one
permission setting: permissions.<list>=<rule> (from .qwen/settings.json, file mtime)row perallow,askanddenyentry and onepermission setting: tools.approvalMode=<mode> (from .qwen/settings.json, file mtime), at the file's modification time, line 1, no session id, andproject_paththe project for a project's.qwen/settings.json. The file is read as JSONC; no other key is read, so the provider keys and channel tokens beside them are never emitted.permissions.autoMode(the classifier's hints) is not read.~/.qwen/settings.jsonis a secret glob, so a--no-secretscollection lacks it. A file that is not a JSON object is oneparser: .qwen/settings.json is not a JSON objectrow.
Model rows¶
- A
session_modelrecord is the rowmodel: <modelId> auth=<authType>, ormodel: <old> -> <modelId> auth=<authType>when an earlier record of the session named another model (modelcolumnmodelId). - An assistant record's
modelyieldsmodel: <id>before its rows when no earliersession_modelor assistant record of the session named a model, andmodel: <old> -> <new>when it differs from the model in force, tracked persessionIdwith the record's timestamp and line. - A
logs.jsonmodel_switchentry ismodel: <fromModel> -> <toModel> reason=<reason>(modelcolumntoModel), printed from the entry, not tracked. executionContext.modelId(withauthType) on each user prompt andmid_turn_user_messagerecord is the model in force when the prompt was sent and feeds the same tracking, before the record's own rows:model: <modelId> auth=<authType>ormodel: <old> -> <modelId> auth=<authType>, theauth=part left out whenauthTypeis absent. Source-derived; not observed on any real install.
Past-use rows and secondary sources¶
logs.json is parsed as a secondary source
(Secondary sources).
usage_record.jsonl is parsed as a secondary source, source-derived; not observed on any real
install. Each line is one system row, session usage: project <project> duration
<durationMs>ms models <id>,<id> tools <tool>=<count>,<tool>=<count> (from usage_record.jsonl,
startTime), - for an absent part, at startTime, with session_id the record's sessionId
and project_path its project. No transcript row repeats it, so it is always kept, and for a
deleted session it is the only row (What survives a delete).
Proposed, not parsed: shell_history and checkpoint-<tag>.json as proposed for Gemini CLI
(Parser plan in gemini-cli.md);
the auto-memory directory after a real-install round (Other stores).
Sync, relay and origin rows¶
The inherited Gemini CLI features are covered in
Sync, relay and origin rows of gemini-cli.md; Qwen
Code changed them as follows, at the commit in Source and evidence.
- The Clearcut logger and Conseca are gone (neither name occurs in
packages/core/srcorpackages/cli/src). Usage statistics go instead to Alibaba Cloud RUM (Real User Monitoring) atgb4w8c3ygj-default-sea.rum.aliyuncs.com, on by default underprivacy.usageStatisticsEnabled(core/src/telemetry/qwen-logger/qwen-logger.ts:80, 215-217;cli/src/config/settingsSchema.ts:1461-1466). The prompt event carries its id and length, not its text (qwen-logger.ts:549-556), user feedback a rating (1044-1053), and error text is replaced by***REDACTED***before an event is queued (146-147, 247-262). OpenTelemetry export is as in Gemini CLI (Sync, relay and origin rows ofgemini-cli.md): off by default, prompt text in the record whentelemetry.logPromptsis on (core/src/config/config.ts:3398-3405;core/src/telemetry/loggers.ts:282-283). - There is no Code Assist backend and no
/privacyopt-in;chat.qwen.aiis used for OAuth only (core/src/qwen/qwenOAuth2.ts:28-31). /export [md|html|json|jsonl] [path]writesqwen-code-export-<ISO time>.<ext>to the working directory or the path given; it sends nothing (cli/src/ui/commands/exportCommand.ts:78-100, 492-500;cli/src/ui/utils/export/utils.ts:10-13)./bugopens a GitHub issue URL and writes no history file (cli/src/ui/commands/bugCommand.ts:33-47).- New: channels. A session driven by
qwen channel startis conducted on a messaging platform, so its prompts and replies sit on that platform's servers as well as in the local transcript. The marker is the channel routes file in Channel routes: an entry names thesessionIdand the platform'schannelName,chatId,senderIdandthreadId. The parser yields onesystemrow per entry, withsession_idthe entry'ssessionIdandproject_pathitscwd:session relayed: channel <channelName> chat <chatId or -> sender <senderId or -> thread <threadId or -> (from channels/sessions.json, startedAt), atstartedAtwhen present and otherwise at the file's modification time with the text endingfile mtime), andchannels/daemon/<hash>/routes.jsonnamed in place ofchannels/sessions.jsonfor the daemon form. The row issession relayed:, notsession synced:: the copy is on the messaging platform, not in a Qwen account, so the parser does not setrecords_sync. A file or an entry that is not a JSON object is oneparser:row. Fixture only: no real install has shown either file. A clean shutdown deletessessions.json, so a session whose entry is gone may still have been a channel session; the bot tokens are in thechannelskey ofsettings.jsonor in environment variables it names (docs/users/features/channels/overview.md:28-36, 254-258). The sessions that remain on the platform are deleted, if at all, on the platform, and no local file records that. - New: the Mem0 external-context extension
(
integrations/external-context-mem0, Mem0 being a memory service an administrator configures) sends the submitted prompt as a search query when its Auto Recall hook is installed, and an opt-incontext_remembertool writes content to that service; both are off unless an administrator installs and configures them, and a write appears in the transcript only as that tool call. No other local marker records it. - Qwen Live (
packages/qwen-live, a voice daemon that drives coding sessions) keeps its own logs and memory under~/.qwen-live(qwen-live/src/config.ts:867-869); it streams audio to the DashScope realtime model (Alibaba Cloud's model API), which is the model API, not sync. - Not reviewed in depth: the Qwen Code Desktop app and its Local Control setting, which shares the live daemon with a phone on the same network (Qwen Code Desktop); whether either leaves a local record of a paired device was not determined.
Scratch directory rows¶
A record whose cwd lies under
<project>/.qwen/worktrees/, or a .worktree.json sidecar, should
yield scratch directory: <worktreePath> (enter_worktree <slug>). The paths are in
Scratch workspaces and worktrees.
Deletion rows¶
No row; nothing marks a removed session except its
surviving usage_record.jsonl entry and logs.json prompts. See
What survives a delete.
Bad-line rows¶
Each line of a transcript that does not decode becomes its own parser: unparseable line row.
logs.json, a JSON array, is decoded record by record: the records before a cut are yielded and the
cut is one unparseable record <N> row. A channel routes file or a trustedFolders.json that is
not a JSON object is one whole-file parser: row, and a routes entry that is not an object is its
own row (Channel routes, Permission rows).
Each line of usage_record.jsonl that does not decode is its own parser: unparseable line row,
and the lines before and after it are still read. A settings.json that is not a JSON object is
one whole-file parser: row.
Fixtures¶
Fixture:
{"uuid":"0f0e0d0c-3333-4000-8000-000000000010","parentUuid":null,"sessionId":"e5f6a7b8-4444-4000-8000-000000000011","timestamp":"2026-10-01T10:00:00.000Z","type":"user","provenance":"real_user","cwd":"/home/alice/proj","version":"0.24.7","gitBranch":"main","promptId":"e5f6a7b8-4444-4000-8000-000000000011########1","message":{"role":"user","parts":[{"text":"run the tests"}]},"systemPayload":{"displayText":"run the tests","hookContext":""}}
{"uuid":"1a1b1c1d-5555-4000-8000-000000000012","parentUuid":"0f0e0d0c-3333-4000-8000-000000000010","sessionId":"e5f6a7b8-4444-4000-8000-000000000011","timestamp":"2026-10-01T10:00:03.000Z","type":"assistant","provenance":"assistant_output","cwd":"/home/alice/proj","version":"0.24.7","gitBranch":"main","model":"qwen3-coder-plus","message":{"role":"model","parts":[{"text":"Plan: run npm test.","thought":true},{"text":"Running tests."},{"functionCall":{"id":"call_abc123","name":"run_shell_command","args":{"command":"npm test"}}}]},"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":30,"totalTokenCount":230},"contextWindowSize":131072}
{"uuid":"2b2c2d2e-6666-4000-8000-000000000013","parentUuid":"1a1b1c1d-5555-4000-8000-000000000012","sessionId":"e5f6a7b8-4444-4000-8000-000000000011","timestamp":"2026-10-01T10:00:09.000Z","type":"tool_result","provenance":"tool_result","cwd":"/home/alice/proj","version":"0.24.7","gitBranch":"main","message":{"role":"user","parts":[{"functionResponse":{"id":"call_abc123","name":"run_shell_command","response":{"output":"12 passing"}}}]},"toolCallResult":{"callId":"call_abc123","status":"success","resultDisplay":"12 passing","errorType":null}}
{"uuid":"3c3d3e3f-7777-4000-8000-000000000014","parentUuid":"2b2c2d2e-6666-4000-8000-000000000013","sessionId":"e5f6a7b8-4444-4000-8000-000000000011","timestamp":"2026-10-01T10:00:10.000Z","type":"system","subtype":"custom_title","provenance":"system","cwd":"/home/alice/proj","version":"0.24.7","gitBranch":"main","systemPayload":{"customTitle":"Run tests","titleSource":"auto"}}
Permission, approval-mode and usage records at
5dcf7c7
(a user record's executionContext, a ui_telemetry tool-call decision, a daemon's
session_approval_mode and a usage_record.jsonl line), shapes from
Permission records and usage_record.jsonl:
{"uuid":"p1","parentUuid":null,"sessionId":"e5f6a7b8-4444-4000-8000-000000000044","timestamp":"2026-10-03T09:00:00.000Z","type":"user","provenance":"real_user","cwd":"/srv/proj","version":"0.25.3","executionContext":{"modelId":"qwen3-coder-plus","authType":"qwen-oauth","approvalMode":"default"},"message":{"role":"user","parts":[{"text":"clean the build"}]}}
{"uuid":"p3","parentUuid":"p2","sessionId":"e5f6a7b8-4444-4000-8000-000000000044","timestamp":"2026-10-03T09:00:09.000Z","type":"system","subtype":"ui_telemetry","cwd":"/srv/proj","version":"0.25.3","systemPayload":{"uiEvent":{"event.name":"qwen-code.tool_call","event.timestamp":"2026-10-03T09:00:09.000Z","call_id":"call_p1","function_name":"run_shell_command","status":"success","decision":"auto_accept","prompt_id":"p#1"}}}
{"uuid":"p4","parentUuid":"p3","sessionId":"e5f6a7b8-4444-4000-8000-000000000044","timestamp":"2026-10-03T09:01:00.000Z","type":"system","subtype":"session_approval_mode","cwd":"/srv/proj","version":"0.25.3","systemPayload":{"mode":"plan","prePlanMode":"default"}}
{"version":1,"sessionId":"e5f6a7b8-4444-4000-8000-000000000055","timestamp":1790935200000,"startTime":1790931600000,"project":"/srv/proj","durationMs":3600000,"models":{"qwen3-coder-plus":{"requests":4,"inputTokens":100,"outputTokens":20,"cachedTokens":0,"thoughtsTokens":0,"totalTokens":120}},"tools":{"totalCalls":3,"totalSuccess":3,"totalFail":0,"byName":{"run_shell_command":{"count":2,"success":2,"fail":0},"read_file":{"count":1,"success":1,"fail":0}}},"files":{"linesAdded":4,"linesRemoved":1}}
Source host rows¶
The agent records no connected source-host account in a file the parser reads. The GitHub and
GitLab channels act as an account through a personal access token in a channels entry of
settings.json or, for GitHub, through the host's own gh login
(Credentials); the authenticated login is looked up at connect and kept in memory
(the GitHub channel also writes it to the daemon's standard error), never in a file
(channels/github/src/GithubAdapter.ts:563-603,
channels/gitlab/src/GitlabAdapter.ts:69-98).
The GitHub
channel appends a publication audit per workspace,
~/.qwen/channels/<workspace scope>/<channel>-<hash>-github-audit.jsonl, whose records name the
repository, issue or pull request number, sessionId, actor and commentUrl of each reply
it posted (GithubAdapter.ts:271-290,
2064-2075);
and a session that opened or reviewed a pull request keeps chats/<sessionId>.pr.json,
{prs: [{number, url, createdAt, state?, source?, issues?}]}
(core/src/services/session-pr-service.ts:15-43).
Neither is parsed yet. No repository list is cached. Grepped for github, gitlab,
bitbucket, oauth, installation, repositor, pull request and clone at
5dcf7c7.
Confidence¶
- High: every
storage.ts, session, token and settings path; the record field names, status values, ISO timestamps and sidecar names. - Medium: the directories named only in the docs (
channels/<scope>/...,log/otel-*,paste-cache); that~/.qwen/QWEN.mdrather thanmemory.mdis the live global memory file (the code definesmemory.md, the docs and the memory tool's users point atQWEN.md); the unit ofturn_result.startedAtandendedAt(numbers, inferred milliseconds); the serialised value oferrorType. - Not determined: whether
.qwen/sandboxever existed; whetherextension-secrets-v1.jsonis used by anything other than MCP extension secrets; the exact serialisation of alogs.jsonmodel_switchentry; whathookContextholds.