Files
qwen-code/docs/developers/daemon/17-configuration.md
T
f2e069061a feat(web-shell): add adaptive navigation rail and unified Live settings (#12943)
* feat(web-shell): add adaptive navigation rail and unified Live settings

* style(web-shell): add space below the Home action section

* Revert "style(web-shell): add space below the Home action section"

This reverts commit a2e1c76223.

* fix(web-shell): Preserve navigation and Live drafts across refreshes

* fix(web-shell): keep rail navigation state, drag widths, and Live settings drafts consistent

Address the Critical findings from the latest review rounds:

- Build the standalone shell sidebar config from the sidebar's exported
  defaults so the Agents entry can no longer silently drop out.
- Gate rail navigation's persisted uncollapse behind restoresColumn so
  opening a full page (Settings, Plugins, ...) no longer rewrites the
  user's collapse preference; only Home/Channels/Live restore the column.
- Reset the sidebar section when a panel closes and stop leaking 'split'
  as a sidebar page, so a wide split keeps the home column and a working
  collapse control.
- Clamp drag widths to the container-aware cap the CSS already renders,
  floor the visual drag at the layout minimum, and restore the persisted
  width from state instead of a mid-animation/container-capped rect.
- Cap the compact drawer's min-width at the container width.
- Keep the Live voice trigger reachable from the Live page header when
  the sidebar's slot is hidden.
- Settle Live settings draft fields (and their conflict baselines) that a
  refresh converges onto, so a later edit is not blocked by a stale
  baseline.

* fix(web-shell): keep rail panel sections, status dots, and Live drafts consistent

Address review findings on the navigation-rail change: retain the
Channels/Live sidebar section when its panel closes (an explicit Home
choice sticks on the first click), load secondary-workspace attention
status whenever the Home column is hidden (idling the poll when nothing
runs), ignore zero-displacement presses on the resize handle, settle
padded Live settings drafts on trimmed refresh convergence, disable the
shortcut capture until status loads, key the empty-chat welcome chrome
off the shell container signal, and document /live in the pre-auth path
lists.

* fix(web-shell): close rail-era gaps in shortcut, dock, and status handling

Address the round-5 review findings on the navigation-rail change. The
Cmd/Ctrl+B handler resolved its target with e.target, which shadow-DOM
retargeting replaces with the portal host, so the editable-target
exemption never fired for portaled dialogs; it now reads
composedPath()[0] like its siblings. The same handler toggled the raw
sidebarCollapsed state, silently inverting the persisted preference in
the split-view fold band where the sidebar renders folded regardless;
the shortcut, the sidebar prop, and the environment-panel dock budget
now share one effective-collapsed expression. The rail's 56px is
excluded from the dock budget so the environment panel keeps docking at
1440px windows instead of being force-closed on load, with the e2e step
re-pinned at 1440 and the consequence recorded in the sidebar docs. The
secondary-workspace status queries now stay active while the Live
section column is open (the rail Home button is the only status surface
there), and the rail branch resets the project search when Live unmounts
its autoFocus field so a Home round-trip cannot steal focus. The Live
Voice card gates key removal and the required marker on the saved key
state, so a model edit leaving a route can no longer offer removal of a
key that was never stored.

* fix(web-shell): stop silent collapse-preference and drag-width rewrites

Address the round-5 review findings on the navigation-rail change, plus
the residual direction of the round-4 fold-band fix:

- The Cmd/Ctrl+B toggle computed from the effective folded state is a
  constant expansion in the split-view fold band; it now rewrites the
  unchanged preference there instead of flipping a stored `true` to
  `false` on a keypress that changes nothing on screen.
- The rail's column restore fires `onCollapsedChange(false)` only when
  the sidebar is actually collapsed, so a drawer tap on Channels/Live no
  longer silently clears the desktop preference from the forced-open
  drawer.
- The Channels rail entry restores the column only when the Channels
  column exists (`channelNavigationEnabled`), matching openNavigation's
  rule for full-page entries.
- The App's rail-Home callback drops its redundant preference write; the
  sidebar's openNavigation already owns column restoration.
- A drag released at or past the container cap while the cap already
  bound the start width no longer persists the capped width over a wider
  stored preference, and releasing at the drag start restores the
  pre-drag state width as well.
- The Live settings settle effect now runs only on a genuine status
  change, so typing a value that briefly trims onto the saved one no
  longer loses the just-typed characters.

Each behaviour is pinned by a unit case that goes red without its fix.

* fix(web-shell): settle Live drafts only on genuinely changed values

Follow-up to the round-5 settle fix: the daemon SDK returns a fresh
status object per fetch, so keying the settle effect on object identity
still let a 1s install poll or focus refresh with unchanged data
converge a padded draft the user was typing. Compare the settle-relevant
saved values against the previous status instead: only a refresh that
actually moved a saved value settles, while draft-only re-renders and
unchanged polls leave the in-progress text alone. Pinned by `does not
settle a padded draft when a poll returns unchanged values`, red against
the identity comparison and green with value comparison; the convergence
cases stay green.

* fix(web-shell): keep capped drags window-scoped and drop detached rail hints

Address the two round-6 Critical findings:

- The container-cap guard added last round only covered a release past the
  cap. While the cap binds, the rendered edge cannot follow the pointer, so
  the rect-based delta is container-scoped in both directions: a 2px
  leftward jitter stored 298 over a 420 preference, and a later host
  widening rendered the narrow value. Map the pointer delta onto the state
  width whenever the cap bound at drag start, so persistence stays
  window-scoped (418), while the past-cap branch keeps priority and an
  uncapped drag still persists what the user saw.
- navigationHint held a live rail button and was only cleared by DOM
  handlers on the nav. Removed elements fire neither pointerout nor blur,
  so an unmounted rail - or just an unmounted hinted button, when its
  feature gate flips - left the tooltip Popover open against a detached
  node, painting the previous icon label near the viewport origin when the
  rail returned. Clear the hint when the rail is gone or its anchor is
  detached; the check runs every render because a dependency list cannot
  observe a child unmounting while both deps stay unchanged.

Both are pinned by cases that go red without their fix: a leftward jitter
stores 418 (298 without the mapping, with the two neighbouring width cases
still green), and a rail tooltip does not survive a rail -> single -> rail
round trip.

* fix(web-shell): re-baseline hidden-surface and failed-save trackers

Address the two round-7 Critical findings:

- The secondary-workspace completion tracker only re-baselined while the
  status surface was hidden, leaving a frozen running snapshot across a
  visible Home period. Comparing the next refetch against it repainted a
  completion the user watched happen as an unread one. The visible path now
  nulls the per-source baseline, so the first hidden cycle after a visible
  period is baseline-only: only completions actually observed while hidden
  produce a dot. The always-hidden sibling path (and its existing test in
  WebShellSidebar.collapse-persist.test.tsx) is untouched.
- A save rejected after the daemon persisted some of its writes (its
  settings writes land before setEnabled, which rolls back only `enabled`)
  was reported as settings.liveSetup.conflict: the stale apiKey baseline
  compared against the refreshed keyConfigured/storedKey flipped by the
  card's own request. The rejected save now queues a re-baseline of the
  request keys and refreshes, and conflict with those keys is suspended
  until the refreshed status lands and re-anchors them (state, not a ref,
  so the re-baseline provokes the render that clears it).

Both pinned by cases that go red without the fix: no completed badge after
a plugins -> home -> plugins round trip while the sibling always-hidden
case stays green, and no conflict plus a retryable Save after a partially
landed save.

* fix(web-shell): stop certifying unfreshed secondary workspace status

Address the round-8 Critical (body-level, independently confirmed by
qqqys): the secondary catalog page is loaded and polled only while the
Home column is hidden, but the rail Home button folded its retained
snapshots into the indicator unconditionally. An approval resolved
elsewhere kept painting the amber dot and the "Waiting for approval"
accessible name until the user hid the Home column again — two render
sites for one stale certification, and a regression over the pre-PR
surface, which could only render the dot from a live, fetchable path.

build the rail indicator from the primary page plus only those secondary
sessions whose queries are currently armed. While the Home column is
visible the secondary contribution is dropped entirely; hiding the Home
column re-arms the queries and the still-waiting state comes back. On
live-state daemons the secondary catalog path is unloaded regardless, so
nothing live is hidden by this gate. Pinned by a plugins -> home ->
plugins round trip asserting both the dot attribute and the accessible
name, red without the gate while the always-hidden attention case stays
green.

* fix(web-shell): gate the completion tracker baseline on page freshness

Address round-9 Critical R9-1: nulling the secondary baseline on the
visible path closes only half the leak. On re-entering the hidden
surface the tracker installed the store's retained, never-refetched
catalog page as the new baseline (the guard rejected only loading and
error, never a stale page), so the poll arriving seconds later was
diffed against a snapshot taken before the visible window and a
completion the user watched was repainted as unread — exactly what the
visible-path null was supposed to prevent.

Stamp the moment the surface becomes hidden and refuse to install or
compare any secondary snapshot that is not provably fresh
(`(snapshot.updatedAt ?? 0) < hiddenSince`, an unstamped page counting
as not-fresh). The first after-hidden poll then installs the baseline
from truly fresh data. The strengthened round-trip case drives retained
running -> stale re-entry -> fresh idle; it is red without the gate
(the completion badge and the Finished accessible name both appear) and
green with it, while the always-hidden completion case stays green.

* fix(web-shell): clamp every collapse entrance and clear More on rail teardown

Address round-9 Criticals R9-2 and R9-3 (body-level):

- The split-view fold-band clamp lived only at the Cmd+B entrance; the
  rail's Collapse/Expand button reached handleSidebarCollapsedChange
  unclamped, so a click that visibly does nothing still overwrote the
  stored preference and a stored `true` could never be re-written inside
  the band. The clamp now lives once in the shared writer: in the fold
  band it re-stores the current raw preference regardless of which value
  was requested. (`sidebarCollapsed` and `splitFoldedSidebar` reach the
  stable []-callback through refs — the former would close over the
  mount-time value and the latter is declared later, TDZ.) The Cmd+B
  site shrinks back to passing the toggle intent. Pinned by a third arm
  in the fold-band App test that clicks the collapse toggle at 1100px
  and finds the stored `true` untouched — red with the clamp removed.
- The More reset effect did not watch layout, so a rail -> single ->
  rail round trip left moreOpen=true against an unmounted owner and the
  rail returned with a phantom open menu (content in the document,
  focus inside, never reopened). `layout` joins the dependency list and
  a round-trip case in WebShellSidebar.collapse-persist.test.tsx goes
  red without it.

* fix(web-shell): let exit-split navigation restore the column under the fold clamp

Address maintainer P2 inline: after the shared-writer clamp landed, a
Home click from the split-view fold band was still intercepted — the
rail's restoresColumn `onCollapsedChange(false)` wrote the folded raw
value back (correct for taps that stay in split view), and `onOpenHome`
then closed split view with the raw preference unchanged, leaving the
Home column folded until a second click.

Split the intent at the shared writer: `exitFoldBand` marks entrances
that reorganize the layout away from split view, bypassing preservation,
and the rail Home handler uses it when the fold band was the overriding
state at click time. Taps that stay in the fold (Cmd/Ctrl+B, the rail
Collapse/Expand button) keep their preference protection untouched, and
the write still never happens inside the mobile drawer, where
splitFoldedSidebar is forced false by construction — so the drawer case
is unaffected (drawer protection is preserved).

Pinned by a fourth arm in `does not invert the stored collapse
preference in the split-view fold band`: stored `true` at 1100px split
view, click `[data-testid="open-home"]` once, and the column is expanded
with the stored key `false`; dropping the bypass inverts that arm while
the three protected arms stay green.

* fix(web-shell): cover Channels and Live in the exit-split fold exception

Address maintainer P2 follow-up: the exitFoldBand carve-out applied only
to Home, so Channels and Live — whose `openPanel` call exits split view
through the web-shell page set (`setMainView('chat')`) the same way —
still had their restoresColumn write clamped when the fold band owned
the sidebar, leaving the section column folded until a second rail tap.

Extent the same bypass to those two entrances, with each entry's own
column-existing gate: Channels applies it only when channelSidebarEnabled
would actually produce a column (mirroring the rail's
channelNavigationEnabled rule, which still leaves the full-page channels
variant folded), and the Live handler inherits the liveSidebarEnabled
guard it's already defined under.

Pinned by two parallel cases beside the fold-band keyboard/click arms:
stored `true` at 1100px split view → one rail tap on `[data-testid=
"open-channels"]` (with `session_source_metadata`) and one on
`[data-testid="open-live"]` (with showLive) — each expands the column and
writes `false`; dropping both bypasses turns both cases red while the
protected Home arm stays green.

---------

Co-authored-by: 钉萁 <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-10-05 08:13:30 +00:00

71 KiB

Configuration Reference

Overview

This page collects every setting that affects the qwen serve daemon and its adapters: environment variables, CLI flags, settings.json keys, and programmatic options. Feature-specific pages link back here when they need cross-cutting configuration details.

CLI flags (qwen serve)

Flag Type Default Effect
--hostname <host> string 127.0.0.1 Bind address. Loopback values: any 127.0.0.0/8 address, localhost, ::1, [::1]; localhost is resolved once and pinned, and trusted mode also verifies the actual listener address before startup completes. A non-loopback result always carries a bearer token: operator-supplied, or generated at startup when neither --token nor QWEN_SERVER_TOKEN is present — generation keys on the requested spelling, so a localhost that resolves off-loopback never generates, and boots only when a token source resolved (the refusal reads the resolved address), while a non-literal name that resolves to loopback generates and prints its bearer token-only. host:port input is rejected with guidance to use --port.
--port <n> number 4170 Listen port; 0 means ephemeral.
--token <s> string env Bearer token. Overrides QWEN_SERVER_TOKEN and is trimmed at boot. It appears in the process command line, so prefer env in deployments. When neither this flag nor the env var is present, a non-loopback bind generates an ephemeral 128-bit base64url bearer (22 characters) and prints it once at startup; loopback spellings generate nothing. An explicitly empty value is a supplied source, not an absent one: it shadows the env var, so a non-loopback bind refuses to boot, and on loopback it selects the trusted token-less mode (or refuses under --require-auth).
--open boolean false Open the mounted Web Shell after runtime readiness. A resolved token (configured or generated) is delivered as a URL fragment. Bare --open remains a silent no-op when browser launch is ineligible.
--open-with-auth boolean false Open the Web Shell with bearer authentication on loopback. Requires an enabled Web Shell and built assets. Reuses a selected --token / QWEN_SERVER_TOKEN, or generates a process-lifetime 256-bit bearer before listen — that generated value is installed on the options token before boot, which makes this flag a third token source that satisfies --require-auth on loopback. In a browser-ineligible environment, starts and prints the secret-bearing fragment URL. Not a ServeOptions or SDK setting.
--require-auth boolean false Extends bearer auth to loopback and /health. Boot refuses to start when no token source resolves, which after the non-loopback generation step means a loopback bind: on a non-loopback bind the generated ephemeral token satisfies the flag, so its "no configured secret, no daemon" fail-fast is loopback-only. Loopback token sources are --token, QWEN_SERVER_TOKEN, and --open-with-auth.
--workspace <dir> absolute path / repeatable process.cwd() Startup workspace runtime; repeat to register additional isolated runtimes. The first is primary. Every value must be absolute and a directory; canonicalized at boot.
--memory-project-scope <mode> git-root / workspace workspace Project-memory partitioning. workspace isolates by exact workspace directory; git-root is the legacy compatibility scope shared by workspaces at the same Git root. Overrides QWEN_CODE_MEMORY_PROJECT_SCOPE.
--max-sessions <n> number 32 Per-workspace active session cap. 0 / Infinity means unlimited; NaN / negative values throw.
--max-total-sessions <n> number 800, or derived at capacity 25 or less Daemon-wide active session cap. When omitted, it is 800 at registration capacities above 25 (including the default 256), even with one workspace. At capacities of 25 or less it is derived once from the per-workspace cap and the startup/restored workspace count, and one such workspace is unlimited. 0 / Infinity means unlimited.
--max-pending-prompts-per-session <n> number 5 Accepted but pending/running prompt cap per session. Excess prompt returns 503. 0 / Infinity means unlimited; negative or non-integer values throw.
--max-connections <n> number 256 HTTP listener server.maxConnections; 0 / Infinity means unlimited.
--enable-session-shell boolean false Enables direct POST /session/:id/shell execution. Effective with bearer auth or trusted-loopback authority; every call must carry a session-bound X-Qwen-Client-Id.
--event-ring-size <n> number 8000 Per-session SSE replay ring; soft cap is 1_000_000.
--session-prompt-settled-close-grace-ms <n> non-negative integer 0 Grace period after a prompt settles before an otherwise-idle session may be auto-closed (ms). Poll-based SSE clients use this window to reconnect without triggering a session rebuild. 0 = immediate close.
--compacted-replay-max-bytes <n> positive integer 4194304 Byte cap for the bounded in-memory replay snapshot returned by POST /session/:id/load; hard cap is 268435456.
--max-journal-events <n> positive safe integer 10000 Per-session baseline cap on in-flight liveJournal replay entries for the unfinished turn. Adaptive growth can raise it (see --max-journal-bytes); pinning either journal flag disables growth.
--max-journal-bytes <n> positive safe integer 8388608 (8 MiB) Per-session baseline byte cap on the in-flight liveJournal. When a turn breaches it, adaptive growth raises the session's caps on demand, toward double but limited by the remaining pool headroom and never past a 256 MiB per-session hard cap — within one daemon-wide pool of 5% of the effective --memory-budget-mb (capped at 1024 MB; 0 — growth disabled — when the effective budget is below the 1024 MB minimum), shared by every workspace bridge; without headroom the oldest entries are dropped with a history_truncated marker. Pinning either journal flag disables growth.
--memory-budget-mb <n> integer in [1024, 1048576] 50% of cgroup-constrained or host memory, capped at the flag maximum (1048576 MB) Total memory budget for the daemon process tree, capped at resolved available memory. Reported under limits.memory in daemon status; it supplies the fixed child partition selected by --child-heap-mode and adaptive live-journal growth (see --max-journal-bytes), not a total RSS bound. Boot rejects out-of-range values.
--memory-pressure-mode <mode> off | observe observe Whether the daemon derives a memory-pressure level from its own RSS and V8 heap. Both modes report runtime.memory.pressure; only observe raises daemon_memory_pressure. Root process only; no remediation.
--child-heap-mode <mode> string observe off publishes no partition; observe reports it without changing spawning; admit enforces the modeled child count; experimental opt-in enforce also applies the fixed per-child old-space ceiling. Admission may reclaim one eligible idle ACP from another trusted daemon-owned workspace, then retry once; live sessions and pending work are protected. If none qualifies or capacity remains full, the new child is rejected. Both admission modes require managed process wiring and a nonzero partition. enforce normalizes inherited fixed heap flags and rejects percentage flags, reports limits.memory.enforced: true, and requires workload calibration. Restart with admit to roll back; no mode bounds total RSS.
--http-bridge boolean true Stage 1 bridge mode. --no-http-bridge still falls back to http-bridge and prints to stderr.
--mcp-client-budget <n> positive integer unset Sets WorkspaceMcpBudget.clientBudget and forwards it to the ACP child through childEnvOverrides.
--mcp-budget-mode <m> off / warn / enforce warn when budget is set, otherwise off Sets WorkspaceMcpBudget.mode; enforce requires --mcp-client-budget.
--external-tool-guard-mode <m> off / required off Enables the managed ACP external pre-execution Guard. required fails startup unless its loopback provider completes the v1 handshake.
--external-tool-guard-endpoint <url> loopback HTTP(S) origin unset Provider origin used only in required mode. It must be origin-only and use 127.0.0.1, localhost, or ::1; paths, credentials, redirects, and proxy routing are rejected.
--external-tool-guard-timeout-ms <n> integer 100..30000 3000 Per-handshake and per-prepare deadline. A timeout fails startup during the handshake or fails the invocation closed during a turn.
--allow-origin <pattern> repeatable string unset Cross-origin allowlist that replaces the default CORS denial. Wildcard and non-loopback HTTP(S) origins require a resolved token, so on a non-loopback bind the generated ephemeral bearer satisfies both guards and the refusals are loopback-only; tokenless HTTP(S) origins must be loopback.
--allow-private-auth-base-url boolean false Allows /workspace/auth/provider to install localhost / private-network auth provider baseUrl; use only in trusted local development.
--web / --no-web boolean true Serve the built Web Shell SPA at the daemon root (GET /, /assets/*, and /session/:id, /plugins, /channels, /live, /scheduled-tasks, /goals, and /settings document navigations). These entry points are mounted before bearerAuth; API calls still follow configured bearer auth or trusted-loopback authority. --no-web leaves the daemon API-only.
--prompt-deadline-ms <n> positive integer unset Server-side prompt wallclock limit in ms. Timeout aborts and returns an error.
--writer-idle-timeout-ms <n> positive integer unset Per-SSE-connection idle timeout in ms. The daemon closes the SSE connection when no event is sent for this duration.
--channel-idle-timeout-ms <n> non-negative integer 0 ACP child auto-reap delay after runtime work drains. Plain preheat is preserved for first use. An explicit keepalive can extend the configured delay; the longer remaining delay wins.
--initialize-timeout-ms <n> positive integer 10000 ACP child startup deadline (channel factory + initialize handshake) and default request timeout (ms).
--session-restore-timeout-ms <n> positive integer 60000 ACP session load/resume timeout (ms). When this flag is omitted, an explicitly supplied initialize timeout raises the budget but never lowers it below the default.
--permission-response-timeout-ms <n> non-negative integer 0 Shared wall-clock timeout for ordinary permission and ask_user_question responses. 0 or an omitted flag waits indefinitely; a positive value enables the timer.
--session-reap-interval-ms <n> non-negative integer 60000 Session reaper scan interval; 0 disables it.
--session-idle-timeout-ms <n> non-negative integer 1800000 Disconnected-session idle reaping time; 0 disables it.
--rate-limit / --no-rate-limit boolean env / off Enables per-tier HTTP rate limiting for prompt, mutation, and read routes.
--rate-limit-prompt <n> positive integer 10 Prompt request limit per window; requires rate limiting to be enabled.
--rate-limit-mutation <n> positive integer 30 Mutation request limit per window; requires rate limiting to be enabled.
--rate-limit-read <n> positive integer 120 Read request limit per window; requires rate limiting to be enabled.
--rate-limit-window-ms <n> integer >= 1000 60000 Rate limit window length; requires rate limiting to be enabled.
no flag - - QWEN_SERVE_NO_MCP_POOL=1 fully disables the pool.

The Web Shell also exposes /manifest.webmanifest and /sw.js when --web is enabled. They are public bootstrap resources; authenticated API routes keep their configured bearer or trusted-loopback policy.

Environment variables

Read by runQwenServe / Express middleware

Env Effect
QWEN_SERVER_TOKEN Bearer token; trimmed at boot. --token wins. An explicitly empty value counts as supplied rather than absent, so it resolves to no token — and --token '' likewise shadows a set value here. When neither this variable nor --token is set, a non-loopback bind generates an ephemeral bearer and prints it once; the daemon never writes this variable back into the environment.
QWEN_SERVE_DEBUG 1 / true / on / yes (case-insensitive) enables verbose stderr logs. See 19-observability.md.
QWEN_SERVE_NO_MCP_POOL 1 disables the workspace MCP transport pool and falls back to per-session McpClientManager; capabilities stop advertising mcp_workspace_pool / mcp_pool_restart.
QWEN_SERVE_PROMPT_DEADLINE_MS Env fallback for --prompt-deadline-ms.
QWEN_SERVE_WRITER_IDLE_TIMEOUT_MS Env fallback for --writer-idle-timeout-ms.
QWEN_SERVE_RATE_LIMIT 1 / true enables per-tier HTTP rate limiting; CLI --rate-limit / --no-rate-limit wins.
QWEN_SERVE_RATE_LIMIT_PROMPT Env fallback for --rate-limit-prompt.
QWEN_SERVE_RATE_LIMIT_MUTATION Env fallback for --rate-limit-mutation.
QWEN_SERVE_RATE_LIMIT_READ Env fallback for --rate-limit-read.
QWEN_SERVE_RATE_LIMIT_WINDOW_MS Env fallback for --rate-limit-window-ms.
QWEN_SERVE_NEW_FILE_MODE New-file mode policy for daemon text writes: owner (default — NEW files are created 0600, umask-independent) or system (NEW files follow 0o666 & ~umask). Case-insensitive; the literal 0600 is accepted as an alias for owner (no other octal modes are supported), and unrecognized values warn on stderr and keep the 0600 default. Existing files always preserve their mode. See qwen-serve.md — New-file mode for agent text writes.
QWEN_SERVE_SESSION_ATTACHMENTS_ROOT Overrides where session attachments are stored; reads/removes that miss the configured root fall back to the default runtime temp dir so pre-switch attachments stay readable. Accepts an absolute path, a path relative to the daemon cwd, or ~ / ~/…. See qwen-serve.md — Session attachment storage.
QWEN_CODE_MEMORY_PROJECT_SCOPE workspace keys project memory by the exact workspace dir; git-root selects the legacy shared scope. When unset, the daemon injects workspace; unrecognized values warn once and retain the legacy git-root behavior. Propagates via the runtime base env, not childEnvOverrides; --memory-project-scope wins. Each workspace remember/forget/dream lane caps pending tasks at MAX_PENDING = 16; N workspaces allow up to 16·N queued tasks with no daemon-wide cap.

Blank QWEN_CODE_MEMORY_PROJECT_SCOPE values are treated as unset and therefore default to workspace; unrecognized non-empty values still warn once and retain the legacy git-root behavior.

Read by the qwen serve CLI wrapper

Env Effect
QWEN_CODE_EXTERNAL_TOOL_GUARD_TOKEN Non-blank bearer token of at most 8192 UTF-16 code units without control characters, copied into ServeOptions.externalToolGuard only in required mode. The CLI then deletes the ambient value before runtime environments are frozen; ACP children, channel workers, and executor environments also scrub it defensively.

Forwarded to the ACP child through BridgeOptions.childEnvOverrides

runQwenServe builds these per handle so two daemons in one process do not race on process.env. The budget variables are not parent-process env fallbacks for qwen serve; the CLI path must generate them from --mcp-client-budget / --mcp-budget-mode.

Env Effect
QWEN_SERVE_MCP_CLIENT_BUDGET Positive integer string consumed by the ACP child's readBudgetFromEnv().
QWEN_SERVE_MCP_BUDGET_MODE off / warn / enforce.
QWEN_SERVE_MCP_POOL_TRANSPORTS Comma-separated transport allowlist; default pooled transports are stdio,websocket; can explicitly include http,sse.
QWEN_SERVE_MCP_POOL_DRAIN_MS Pool entry idle drain delay; default 30000, clamped to 1000..600000 ms.

Read by SDK / adapters

Env Effect
QWEN_DAEMON_URL Daemon base URL for CLI TUI adapter, channels, and IDE companion.
QWEN_DAEMON_TOKEN Bearer token.
QWEN_DAEMON_WORKSPACE Overrides the cwd sent to POST /session.

settings.json keys

The daemon constructs each workspace runtime from that workspace's merged settings and environment overlay. Process-global listener/auth options are resolved once, while runtime-specific services and ACP children receive the owning runtime snapshot. Malformed settings follow the documented startup fallback or failure behavior for the affected runtime; they must not cause another workspace's settings to be reused.

Key Type Effect
policy.permissionStrategy 'first-responder' | 'designated' | 'consensus' | 'local-only' Sets BridgeOptions.permissionPolicy; the active value appears in /capabilities as policy.permission. Boot validates through validatePolicyConfig() against SERVE_CAPABILITY_REGISTRY.permission_mediation.modes. Unknown literals throw InvalidPolicyConfigError and fail boot explicitly.
policy.consensusQuorum positive integer N for the consensus policy. Default is floor(M/2) + 1 over votersAtIssue.size (M=2 means unanimous; larger even M means more than half). If set under a non-consensus policy, it is ignored and boot prints a stderr warning. Non-positive integers throw InvalidPolicyConfigError. See 04-permission-mediation.md.
context.fileName string Workspace memory filename. qwen serve snapshots it through extractContextFilename() and passes it to the workspace service as contextFilename; POST /workspace/init writes that file.
tools.disabled string[] Tools disabled for the next ACP child spawn. Normalized through normalizeDisabledToolList() (packages/cli/src/config/normalizeDisabledTools.ts): non-array becomes [], non-string entries are skipped, whitespace is trimmed, empty entries are dropped, and duplicates are removed while preserving first occurrence. Boot and restartMcpServer settings refresh both run through this function. ToolRegistry.has(name) is exact and case-sensitive. POST /workspace/tools/:name/enable and tool_toggled update this key.
tools.approvalMode 'default' | 'auto' | ... Default session approval mode; POST /session/:id/approval-mode writes here when persist: true.
telemetry object OTel config. Keys include enabled, otlpEndpoint, otlpProtocol, otlpTracesEndpoint, otlpLogsEndpoint, otlpMetricsEndpoint, target, outfile, userId, includeSensitiveSpanAttributes, sensitiveSpanAttributeMaxLength, resourceAttributes, and metrics.includeSessionId. resolveTelemetrySettings() reads it at boot and initializes initializeTelemetry(). userId is process-wide and must not be configured as end-user identity when the daemon serves multiple users.

ServeOptions (programmatic embedding)

packages/cli/src/serve/types.ts defines the typed options passed through the public serve APIs. It mirrors the CLI flags above and adds:

Field Effect
eventRingSize Overrides the default per-session ring size.
sessionPromptSettledCloseGraceMs Grace period after a prompt settles before an otherwise-idle session may be auto-closed. 0 = immediate close.
memoryProjectScope runQwenServe only; precedence is option, launch env, then workspace. Direct createServeApp callers use deps.daemonEnv.
maxPendingPromptsPerSession Pending prompt cap per session; 0 / Infinity means unlimited.
mcpPoolActive Programmatic switch, defaulting from QWEN_SERVE_NO_MCP_POOL.
externalToolGuard Optional {mode:'required', endpoint, token, timeoutMs?}. Omission is fully off; required mode performs the provider handshake before listening.
allowOrigins Cross-origin allowlist (string[]), corresponding to --allow-origin.
allowPrivateAuthBaseUrl Allows private / localhost auth provider baseUrl installation.
serveWebShell Serve the built Web Shell SPA at the daemon root (default true); false (the CLI's --no-web) leaves the daemon API-only. No effect when the build omits the shell assets.
enableSessionShell Enables session shell execution; bearer auth or trusted-loopback authority and a session-bound client id are still required.
promptDeadlineMs Prompt wallclock limit.
writerIdleTimeoutMs SSE writer idle timeout.
channelIdleTimeoutMs ACP child auto-reap delay after runtime work drains. Plain preheat is preserved for first use; active keepalive windows may extend the delay.
initializeTimeoutMs ACP child request timeout, including the initialize handshake.
sessionRestoreTimeoutMs ACP session load/resume timeout. Precedence: explicit restore value; otherwise an explicit initialize value raises the 60000 default but never lowers it; otherwise 60000.
sessionReapIntervalMs Session reaper scan interval.
sessionIdleTimeoutMs Disconnected-session idle reaping time.
rateLimit* Per-tier HTTP rate limit switch, thresholds, and window.

BridgeOptions (programmatic bridge embedding)

packages/acp-bridge/src/bridgeOptions.ts defines bridge options. See 03-acp-bridge.md for the full table. Key fields:

Field Effect
boundWorkspace Required canonical workspace.
sessionScope 'single' (default) vs 'thread'.
initializeTimeoutMs, sessionRestoreTimeoutMs, maxSessions, eventRingSize, permissionResponseTimeoutMs, maxPendingPermissionsPerSession Bounded resource caps. permissionResponseTimeoutMs is shared by ordinary permissions and questions; 0 or omission disables its timer.
channelFactory Pluggable ACP child factory; default is defaultSpawnChannelFactory.
fileSystem BridgeFileSystem adapter. See 07-workspace-filesystem.md.
permissionPolicy, permissionConsensusQuorum, permissionAudit Mediator wiring.
statusProvider Daemon-host preflight cells.
childEnvOverrides Per-handle environment additions or removals.
externalToolGuard Optional daemon-side handler for the private child-to-parent prepare RPC. The bridge validates channel ownership and the active Prompt before and after it calls the handler.
channelIdleTimeoutMs ACP child auto-reap delay after runtime work drains. Plain preheat is preserved for first use; active keepalive windows may extend the delay.

Web Shell workspace creation deadlines

In the common case, workspace creation makes a capability preflight on a cold or expired cache, followed by session creation. Each request has its own default 30-second timeout, including response-body consumption. Web Shell allows 75 seconds for the combined creation action, covering these two request budgets plus 15 seconds of headroom. A 20-second preflight followed by a 15-second create therefore succeeds without configuration changes. A concurrent capability refresh can supersede the preflight and extend the chain; the action may then time out before creation settles even when each request stays within its own deadline.

The 75-second limit also bounds transports that do not settle after SDK cancellation. It limits the action's wait rather than guaranteeing transport cancellation; successful results arriving after that limit are detached. Both initial workspace creation and creation with an existing session use this limit. SDK standalone creation, other actions, and post-creation callbacks retain their existing deadlines.

Increasing --initialize-timeout-ms does not raise the SDK request timeout for the capability preflight or the create request. Load/resume follows the separate restore timeout contract: automatic SDK derivation requires a cached server restore budget; without one it falls back to 70 seconds. Explicit request or client timeouts override that derivation and can shorten the budget. See the serve protocol reference for the SDK and Web Shell restore headroom.

Direct SDK consumers can configure fetchTimeoutMs; Web Shell providers do not expose this option. See the Web Shell timeout reference for creation and callback limits.

The following controlled HTTP test shows the previous 30-second action deadline and the 75-second deadline with identical response delays. It exercises the actual SDK and workspace actions, not a rendered Web Shell or real daemon.

Controlled HTTP workspace creation before and after the deadline change

Important defaults

Constant File Value Meaning
DEFAULT_MAX_SESSIONS bridge.ts 32 Session cap before SessionLimitExceededError.
MAX_EVENT_RING_SIZE bridge.ts 1_000_000 Soft cap for BridgeOptions.eventRingSize; guards against typos.
DEFAULT_RING_SIZE eventBus.ts 8000 Per-session SSE replay ring depth.
DEFAULT_MAX_QUEUED eventBus.ts 256 Per-subscriber queue cap.
DEFAULT_MAX_SUBSCRIBERS eventBus.ts 64 Per-bus subscriber cap.
WARN_THRESHOLD_RATIO eventBus.ts 0.75 slow_client_warning trigger.
WARN_RESET_RATIO eventBus.ts 0.375 Hysteresis re-arm threshold.
DEFAULT_INIT_TIMEOUT_MS bridge.ts 10_000 ACP initialize handshake timeout.
MCP_RESTART_TIMEOUT_MS bridge.ts 300_000 Bridge timeout for /workspace/mcp/:server/restart.
DEFAULT_PERMISSION_TIMEOUT_MS bridge.ts 0 Shared permission and question wallclock; 0 disables the timer.
DEFAULT_MAX_PENDING_PER_SESSION bridge.ts 64 Aligned with DEFAULT_MAX_SUBSCRIBERS.
MAX_RESOLVED_PERMISSION_RECORDS permissionMediator.ts 512 FIFO for recently resolved permissions.
KILL_HARD_DEADLINE_MS spawnChannel.ts 10_000 Per-channel graceful shutdown window.
SHUTDOWN_FORCE_CLOSE_MS run-qwen-serve.ts 5_000 HTTP server force-close timer.
MAX_READ_BYTES fs/policy.ts 256 * 1024 Full-snapshot and returned-text cap; larger UTF-8 text requires a finite line limit.
MAX_WRITE_BYTES fs/policy.ts 5 * 1024 * 1024 Write cap.
MAX_DISPLAY_NAME_LENGTH bridge.ts 256 Session displayName cap.

Cross-references