qwen-code/docs/developers/daemon/17-configuration.md
Shaojin Wen da26cffc36
feat(review): Aone Code read path (second review-platform provider) (#9226)
* docs(design): /review Aone Code read path (Phase 2)

* feat(review): Aone Code read path (Phase 2)

Adds an Aone Code provider so /review can review a MaxCompute CR locally.
The read path works end to end against a real odps_src CR (verified E2E):
fetch-pr fetches `refs/merge-requests/<global-id>/head` and builds the
worktree + diff (stats computed locally, Aone advertises none); meta /
issue-context / fetch-diff resolve identity, Aone workitem evidence, and the
diff via the a1 CLI. The four reader-backed subcommands and fetch-pr select
the provider from the clone's remote (or an Aone host), so a GitHub clone is
unchanged.

Read-only this phase: pr-context / comment-status / presubmit have no Aone
backing yet (the run degrades to context-unavailable), and --comment is
refused on an Aone target. SKILL.md + code-review.md document the Aone
target and the degradations. See docs/design/2026-08-15-review-aone-provider.md.

* fix(review): address PR #9226 round-1 Aone review findings

Critical:
- match-remote: Aone CR URLs use the WEB host (code.alibaba-inc.com) while
  a clone's remote uses the GIT host (gitlab.alibaba-inc.com) — treat them as
  one equivalence class (hostsEquivalent) so a codereview URL matches its
  clone's remote and the worktree flow is reachable; nested-group remotes
  (group/subgroup/project) now collapse to the last two segments instead of
  failing to match
- registry: an explicit non-Aone host/remote now beats the cwd probe, so an
  explicitly-GitHub subcommand run from an Aone clone is not hijacked to
  Aone; hint host is trimmed; the four reader-backed subcommands thread
  --host into detection (previously dropped); dropped the unwired --platform
  dead switch
- aone parseRemoteUrl: user-less scp remotes (ssh-config/insteadOf), nested
  groups, a trailing slash after .git, and empty segments now parse; the
  parse-failure message redacts a user:token@ origin (no credential leak)
- aone getCommentBody throws on a missing id (was an indistinguishable empty
  string); fetchDiff uses gitRaw (512 MiB buffer, no CRLF rewrite, latin1)
  instead of git() (1 MiB ENOBUFS, CJK byte loss)

Suggestions:
- aone-client: 120 s timeout, ENOENT branch in the auth check ("install the
  a1 CLI"), TRANSIENT_RE anchored to HTTP 5xx (bare 502/503/504 misfired on
  command lines containing those digits), one stderr trace line per retry
- fetch-pr: validate pr_number before Number() coercion (1e3 fetched PR
  1000), trim --host before detection; submit guard trims --host;
  comment-body --pr help notes the Aone per-MR requirement
- parse-args: nested-group codereview URL grammar; invalid-url warning names
  both grammars
- SKILL.md + code-review.md: Aone paragraph corrected (clone-origin trigger,
  Agent 0 skipped, test-plan/publish-assets unbacked, pass --host), design
  doc updated (detection, Agent 0 gating)
- tests: aone.test.ts, registry cwd-mock + precedence + parseRemoteUrl cases,
  remote-match hostsEquivalent + nested collapse, parse-args nested codereview,
  submit-aone refusal

* fix(review): address PR #9226 round-2 Aone review findings

Critical:
- parse-args: the Aone CR URL grammar is now constrained to Aone hosts
  (*.alibaba-inc.com) — a /codereview/ URL on any other host hits the
  fail-closed invalid-url refusal instead of becoming a live PR target
  (unlike …/pull/<n>, which any GHE host legitimately serves)
- aone fetchDiff: merge-bases against a fetched target branch (not a
  present-but-stale origin/<target>), and the MR-head refspec is now
  force-fetched (+) so a stale throwaway ref from an interrupted run does
  not fail the fetch when the head was rewritten (normal AGit-Flow iteration)

Suggestions:
- fetch-pr: countDiffChangedLines now delegates to the single hunk-state
  walker in computeDiffStats (the two could not disagree silently); the
  changedFiles count is pinned on `diff --git` via a binary-file fixture
- aone parseRemoteUrl scheme case made explicit + pinned (RFC 3986)
- submit.test.ts pins the platform registry to GitHub so the Aone refusal
  guard neither spawns a real git in the vitest cwd nor couples to the
  machine's clone origin

* fix(review): address PR #9226 round-3 Aone review findings

Critical:
- registry: hostOfRemoteUrl now makes `user@` optional in the scp branch
  (user-less scp remotes from ssh-config/insteadOf no longer misroute an
  Aone clone to GitHub); the token-bearing scp userinfo parses to the host,
  not an owner
- parse-args: the Aone CR-URL host group now requires a REAL subdomain dot
  boundary (`(?:[A-Za-z0-9-]+\.)+alibaba-inc.com`), so lookalikes
  (`evilalibaba-inc.com`) hit the fail-closed refusal; a `/pull/<n>` URL on
  an Aone host is refused too (Aone serves no /pull/ pages)
- meta: on a non-GitHub platform an explicit `--repo` without `--host` is
  refused (no default host off GitHub) instead of emitting the contradictory
  `platform:aone` + `host:github.com`
- aone fetchDiff: spreads PINNED_DIFF_CONFIG/PINNED_DIFF_FLAGS (an un-pinned
  color.diff=always zeroes computeDiffStats), discloses a failed target-branch
  fetch via a stderr WARNING, and refuses to diff from a clone of a different
  repo; scp-form userinfo is redacted in the parse-failure message

Suggestions handled:
- comment-body: the Aone per-MR `--pr` requirement is enforced before the
  auth gate (usage errors precede auth)
- aone-client: the auth-failure diagnostic surfaces a1's real first stderr
  line (not the execFileSync preamble) and reports a timeout/kill distinctly
- remote-match docstring + registry precedence comment updated to the
  implemented behavior

Deferred to follow-up #9194: the 16 test-gap patterns, headRefOid dead-field
removal, MAX_SAFE_INTEGER digit guard, and the refusal-message host branch.

* fix(review): address PR #9226 round-4 Aone review findings

Critical:
- aone fetchDiff + fetch-pr merge-base fetch: the server-controlled
  target/base branch reached `git fetch` bare — a dash-leading branch name
  (creatable by full-refname push) parses as an option, so
  `--upload-pack=<payload>` executed attacker-named code with the
  reviewer's credentials. Pass `--` to end option parsing and refuse
  dash-leading values outright on both providers
- meta: the no-default-host guard now gates on the FLAG, not the resolved
  value — a GH_HOST export no longer bypasses it (and an empty-string
  --host counts as missing); the whole --repo branch's pure resolution
  moves above the auth gate (usage errors precede auth)
- skill: pass --host for EVERY pr-url target including github.com — an
  omitted hint falls back to the cwd origin probe, which hijacked a
  github.com review run from an Aone clone (and vice versa); lightweight
  fetch-diff/pr-context carry the host too
- submit: the Aone refusal moves BELOW the authorisation gate and takes
  the exit-3 + {"posted": false} shape instead of throwing — an
  unauthorised Aone run now ends as the skill's contract defines, and
  detection reads the effective host (flag → GH_HOST), so an Aone-pointing
  GH_HOST export is refused instead of dying opaque inside gh

Suggestions handled:
- parseRemoteUrl: strip query/fragment (credential channel into repo
  identity), fix the cleaning order for two-plus trailing slashes after
  .git, and discard an explicit port instead of folding it into the path
- registry: isAoneHost normalizes the trailing-dot FQDN spelling; the cwd
  probe delegates to lib/git's gitOpt (shared git policy)
- aone: the MR-head refspec is stated once (mrHeadRefSpec); resolveRepo
  quotes git's real error line, not the execFileSync preamble
- aone-client: the auth fall-through message is neutral (covers
  non-auth failures the login hint cannot fix)
- fetch-pr: pr_number guard tightened to ^[1-9]\d*$ (no PR zero, no
  leading zeros, no side effects before the refusal)
- the five detection-consuming subcommands' --host describes now state
  the implemented semantics; SKILL.md/code-review.md read-only phrasing
  corrected and the false "detection reads the clone's remote, not the
  URL" claim fixed

Tests: dash-leading refusal on both providers, meta guard flip tests
(GH_HOST bypass, empty flag, pre-auth), submit exit-3 shape (authorised,
unauthorised, padded host, GH_HOST), trailing-dot and /pull/-on-Aone
parse refusals, port/query/slash parse cases, fetch-pr zero-number and
base-ref refusals.

Deferred to follow-up #9194: the single-branch merge-base disclosure
(R3-9), cleanup audit skip-in-code (R3-13), URL-form --remote hint
(R3-19), publish-assets refusal parity (R3-22), and the data-path
deadline translation (R3-25).

* chore(review): re-push to re-link PR head after branch recreation

* test(review): repin SKILL.md host-rule wording in SKILL.test.ts

The round-4 fix rewrote the skill's --host notes (pass --host for every
pr-url target, github.com included); three revert-guard tests pinned the
old 'add --host <host> for Enterprise' phrasing and reddened the core
suite in CI. Repin them at the new wording.

* fix(review): address PR #9226 round-5 Critical findings

- aone resolveRepo: redactUrl now strips the query/fragment channel too —
  a ?private_token=… origin carries no @ for the userinfo redaction, so the
  parse-refusal message echoed the secret the success path strips (test
  pins the refusal message secret-free)
- aone fetchDiff: the merge-base fallback (base = ref~1) DISCLOSES via a
  stderr WARNING — previously silent, a multi-commit MR got only its last
  commit served as the complete diff (shallow/single-branch clones hit
  this; the GitHub path is loud about the same class)
- submit: the Aone write-refusal binds the platform in BOTH directions —
  the authorisation gate now surfaces the recorded target's host, so a
  recorded Aone host refuses whatever the runtime-effective host resolves
  to (an ambient GH_HOST export can no longer steer an Aone review into
  posting at a same-named repo), while a recorded non-Aone pr-url binding
  is no longer vetoed by the cwd probe from an Aone-origin clone

Tests: refusal-message redaction, fallback disclosure (spy calls captured
before mockRestore — vitest's restore clears them), bidirectional refusal
arms (recorded-Aone + GHE env refuses; recorded-github + Aone cwd posts).

* fix(review): address PR #9226 round-6 Critical findings

- authorization: the --user-authorized fast path now surfaces the recorded
  target's host too (best-effort read of the recorded args) — it returned
  before the args file was read, so recordedHost was always undefined on
  that path and the 'a recorded Aone host always refuses' invariant leaked:
  a user-authorised post of a recorded Aone codereview review from a
  non-Aone cwd with no --host/GH_HOST posted at github.com's same-named
  repo. Tests pin the fast-path host through the REAL gate and the
  end-to-end refusal (the witness scenario)
- aone: the query/fragment strip now uses [\s\S]* in both redactUrl and
  parseRemoteUrl — git stores newline-bearing remote URLs, and a plain .
  stopped at the first \n, letting ?private_token=SECRET\nx smuggle the
  token past the strip into the parse-refusal message. Tests cover both
  the parse-success and refusal paths of the smuggle

Round-6 is Critical-only per the ~5-round policy (user-confirmed for
convergence); the 13 Suggestions are deferred to follow-up #9194.

* fix(review): address PR #9226 round-7 Critical findings

- fetchDiff's throwaway ref now carries a pid suffix — two concurrent runs
  for the same MR in one clone shared the name: one session's finally-
  delete killed the other mid-review (unknown revision), and a
  pre-existing local branch of the reserved name was force-moved then
  deleted, reflog and all (race probe: 12/60 failures → 0 with the
  per-run unique name)
- the target/base-ref guards close the refspec channel the dash-only
  check left open after `--`: a leading `+` parses as a force refspec
  (fetches the wrong head — stale evidence, no WARNING) and a colon as
  src:dst (force-moves the throwaway ref or a reviewer-local branch).
  Both providers now refuse '-', '+', and ':' shapes (probe-confirmed on
  real fetchDiff incl. the served-wrong-diff and local-branch-overwrite
  witnesses); tests pin the new channels on both guards
- redactUrl and parseRemoteUrl clean userinfo BEFORE the query/fragment
  strip: a userinfo that itself contains '?' or '#' was truncated
  mid-credential, leaking the username+secret prefix into the refusal
  message and making parseable origins unparseable (flip-verified on the
  witness shapes)

Round-7 is Critical-only per the convergence directive; the 8
Suggestions (incl. the 4 bot findings) are deferred to #9194.

* fix(review): address PR #9226 round-8 Critical findings

- the server-controlled branch-name guards now validate ALLOWLIST-style on
  both providers (aone.fetchDiff's target, fetch-pr's baseRefName): the
  denylist admitted HEAD (silent fetch + merge-base through the stale
  clone-time symref), rev-parse metasyntax (wrong base under a
  misdescribing warning), ranges, and the empty string (garbled diff-less
  fallback) — a plain-branch-name shape closes every channel
- parseRemoteUrl/redactUrl consume userinfo GREEDILY up to the last @ of
  the authority — multi-@ and :-/-bearing token userinfo no longer leaks
  cleartext residue through the refusal messages or folds into the parsed
  host (take() fails closed on any surviving @); the scp strip admits only
  a removal that leaves a host: shape behind
- fetch-pr's Aone stats backfill moves AFTER the plan/rescue, where
  diffText is final — the partition-rescue republishing the full range no
  longer leaves delta-scoped numbers beside a full-range diffPath — and
  isCollapsedFromUpstream is skipped when the stats are locally derived
  (one source, not two: the disclosure needs an independent advertised
  fact, and a delta-scoped round beside the full-range count fired a
  false collapse)
- remote identity is injective again: Aone nested-group targets carry the
  full group path (parse-args → match-remote --group-path → matchRemotes
  compares every segment when both sides have three or more), and
  fetchDiff's origin guard adds the origin's host (Aone family) — a
  same-named repo in another group or on another platform can no longer
  pass either gate; SKILL.md passes --group-path for nested targets
- meta's discovery branch drops GH_HOST inheritance off GitHub — an
  ambient GHE export beside an Aone-origin clone no longer vetoes the
  valid invocation at HOSTNAME_RE; only an explicit --host steers routing

Round-8 is Critical-only per the convergence directive; all five findings
fixed, no deferrals this round.

* fix(review): address PR #9226 round-9 Critical findings

- redactUrl is fail-closed BY CONSTRUCTION: split at the last @, redact
  everything before it — the per-regex redaction kept missing shapes
  (round-9: URL userinfo with a / in the secret, scp userinfo with a
  newline, residues with no host: shape all leaked verbatim through the
  parse-refusal message)
- parseRemoteUrl cleans per form and fails CLOSED: URL-form userinfo is
  bounded to the authority (greedy within it — multi-@ and ?/# inside
  secrets consumed whole, /-bearing secrets left to fail closed in take),
  scheme inputs never fall through to the scp grammar (a malformed
  https://user:pa/ss no longer parses host user); the round-8 scp-strip
  firing on scheme URLs fabricated coordinates from query-borne and
  path-borne @ witnesses — all witnesses now parse correctly or refuse
- registry hostOfRemoteUrl consumes token-bearing userinfo (':' AND '/'
  in the secret) on both branches, mirroring aone.parseRemoteUrl —
  detection no longer parses the credential prefix as the host and
  misroutes Aone clones to GitHub; detectPlatformKind ranks an explicit
  --host above the remote-URL hint in BOTH directions (an Aone origin
  can no longer hijack an explicitly-GitHub invocation into fetching a
  global MR id from the wrong remote)
- nested-group identity is injective in both directions: matchRemotes
  compares the full group path exactly whenever the target carries one
  (any length — a 3+-segment target no longer matches a two-segment
  remote sharing its tail, nor the reverse); Aone CR targets carry the
  path even at two segments and the canonicalized URL keeps the full
  path; fetchDiff's origin guard compares the origin's full path against
  the MR's own detailUrl path (authoritative repo identity, where the
  seam's ownerRepo is collapsed); the rescue pool keys on the full path
  and same-id cross-group CR URLs are refused as ambiguous

Round-9 is Critical-only per the convergence directive; the 8
Suggestions (R8-6..R8-13) are deferred to follow-up #9194.

* fix(review): address PR #9226 round-10 Critical findings

- aone.fetchDiff's host arm keys on the CANONICAL Aone-family predicate
  (new remote-match isAoneHostFamily: port/trailing-dot/case normalized;
  registry.isAoneHost now delegates to it) — a trailing-dot FQDN clone
  that detection accepts as Aone can no longer be refused by the diff gate
  with a misdirecting remedy
- the URL cleaning/redaction class is closed structurally, not per shape
  (sixth consecutive round a new entrance was found): parseRemoteUrl's
  URL-form userinfo is consumed whole WITHIN the authority (span between
  // and the first /), and the scp-form userinfo strip + its lookahead are
  bounded at ?/# — an @ inside a query or fragment value is the
  credential's own character and can no longer fabricate coordinates from
  the query tail; redactUrl fails the DISPLAY closed with a constant when
  the last @ sits after a ?/# marker — the token tail can no longer reach
  the refusal message (URL/scp/fragment witnesses all pinned)
- isPlainBranchName rejects git's pseudo-ref set (FETCH_HEAD/ORIG_HEAD/
  MERGE_HEAD/…) on both guards — FETCH_HEAD resolves to the just-fetched
  PR head (empty diff beside full-range metadata), ORIG_HEAD to an
  arbitrary ancestor; both shape-legal, both silently wrong
- fetch-pr's merge-base probe requires the fetch to have produced the
  tracking ref — a tag-only baseRefName exits 0 writing only FETCH_HEAD,
  and the bare-name fallback once merge-based against the reviewer's
  local tag with baseFetchFailed falsely false; the tag shape now lands
  in the disclosed state
- parse-args: the repo-qualified CR URL outranks a same-number bare
  spelling as the target in BOTH the rescue pool and positional order —
  the bare number carries no host, and letting it win flipped detection
  onto the cwd fallback (a loud refusal at the merge base had degraded to
  a silent wrong-platform retarget); bare restatements of the URL target
  are skipped silently, matching the rescue loop's restatement handling

Round-10 is Critical-only per the convergence directive (the bot's own
ledger is at its round cap); the 6 convergence-posture deferrals named in
the review body join follow-up #9194.

* fix(review): address PR #9226 round-11 Critical findings

- the URL cleaning/redaction surface is closed STRUCTURALLY: one parser,
  one source of truth — registry.hostOfRemoteUrl now delegates to the
  canonical aone.parseRemoteUrl (detection and the identity parser can no
  longer disagree), and the scp branch reads GIT'S OWN grammar
  (GIT_TRACE-probed: hostinfo ends at the FIRST ':', userinfo carries no
  ':' or '/') — the last-'@' consumption once parsed a different host than
  git connects to, letting fetchDiff's same-repo guard pass while git
  fetched from another server; token-bearing scp shapes now fail closed,
  and the round-8 detection tests are re-blessed onto shapes git reads
  that way
- the pseudo-ref allowlist is CASE-INSENSITIVE on both twins: on
  case-insensitive filesystems (macOS/Windows defaults) fetch_head folds
  onto FETCH_HEAD, resolving the merge-base to the just-fetched MR head
  (empty diff beside full-range metadata); lowercase spellings refused,
  pinned
- submit.test.ts's file-level setup now saves/clears/restores GH_HOST —
  the Aone refusal reads the ambient env, and the org's standard intranet
  export pattern (an Aone-family host) turned 50 of 69 posting tests into
  refusals
- the --user-authorized fast path binds the recorded host to THIS write
  (same-PR number only — a stale recording of another PR must not supply
  a host) and scans SIBLING session recordings when the session-scoped
  args file is absent — the characteristic cross-session publish shape
  otherwise lost the host and posted a recorded Aone review at
  github.com's same-named repo (real-gate witness: exit 0, COMMENT filed);
  tests drive the real gate through a sibling-session fixture
- the tracking-ref requirement and both merge-base sites are FULLY
  QUALIFIED (refs/remotes/…): git resolves unqualified origin/<name> in
  refs/tags and refs/heads first, so a tag or branch literally named
  origin/<baseRefName> — a PUSHABLE, server-controlled refname a plain
  clone auto-carries — shadowed the just-fetched tracking ref and moved
  the merge base with no disclosure; shadow-tag tests pinned on the
  resolveMergeBase probe, the fetch-pr seam, and aone.fetchDiff

Round-11 is Critical-only per the convergence directive; the bot's own
ledger is at its round cap and this round still produced findings —
recommend freezing the bot loop and moving to human security review.

* fix(review): address PR #9226 round-12 Critical findings

- the recorded-args host lookup is HARDENED — the store lives under
  .qwen/tmp/ beside review worktrees checked out from the PR's own tree,
  so its content is attacker-influenceable: only s-* session directories
  are scanned (a malicious PR can no longer plant a root-level args file
  that binds a host), symlinks are skipped at both the directory and file
  levels (mirroring writeSkillArgs' O_NOFOLLOW write-side policy), reads
  are size-bounded, and the host binds only when the recording names the
  same PR number AND the same repo
- the canonical Aone invocation shape (bare global MR id, no URL) can no
  longer post cross-session without host evidence: a same-number
  recording with no host binds the recorded --host flag when present
  (parse-args now records it), and without one the write gate FAILS
  CLOSED with the exit-3 shape and names the remedy — instead of posting
  the review at github.com's same-named repo (the probe-verified witness
  once exited 0 and POSTed)
- parse-args: the URL-outranks-bare-number invariant now holds for MIXED
  shapes — a positional bare number restating the rescue pool's single
  PR is carved out of hasValidCandidate, so --effort <cr-url> 7 (and
  both orderings/equals-form) target the CR URL instead of silently
  retargeting onto the cwd clone's same-number PR; a different number
  still outranks
- aoneReader.resolveRepo refuses an origin outside the Aone host family —
  an explicit --host can steer detection onto this reader while the cwd
  clone is a GitHub mirror (the dual-remote migration setup), which once
  emitted {platform:'aone', host:'github.com'} and queried a1 with the
  mirror's coordinates; same predicate fetchDiff's origin guard applies
- isPlainBranchName (both twins) rejects refs/-prefixed names: legal
  branch names (check-ref-format --branch) that resolve qualified refs
  the server controls as fetch/merge-base arguments (refs/remotes/origin/
  HEAD is the clone's default-branch symref — wrong base, misdescribing
  WARNING)
- the ref-dwim class is closed at the verified sites: fetch-pr's base
  probe fetches an EXPLICIT branch refspec (bare names dwim onto
  same-named tags — exit 0, tracking ref untouched, stale base passing
  the freshness guard it never refreshed), its fetchedSha/merge-base head
  reads are refs/heads-qualified (a planted same-name tag can no longer
  shadow the real head), and aone.fetchDiff's target fetch + merge-base +
  diff-range reads are qualified the same way

Round-12 is Critical-only per the convergence directive (the bot's own
ledger is past its round cap).
2026-08-18 09:19:52 +00:00

59 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: 127.0.0.1, localhost, ::1, [::1]. Non-loopback requires a bearer token at boot. 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.
--require-auth boolean false Extends bearer auth to loopback and /health; boot refuses to start without a token.
--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 derived for multiple startup/restored workspaces Daemon-wide active session cap. When omitted, a finite default is derived once from the per-workspace cap and startup/restored workspace count. 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. Requires bearer token, and 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.
--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 does not size any child process — the one consumer today is adaptive live-journal growth (see --max-journal-bytes). 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> off | observe observe Whether the daemon models a per-child heap partition of the budget. observe reports it and counts spawns past it; nothing is applied. off publishes no partition at all — maxConcurrentChildren and perChildCeilingMb are both null.
--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. * allows any origin but requires a token.
--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 document navigations). These entry points are mounted before bearerAuth; every API route stays token-gated. --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 How long to keep the ACP child alive after the last session closes. 0 means reclaim immediately.
--initialize-timeout-ms <n> positive integer 10000 ACP child request timeout, including the initialize handshake (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.
--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.

Environment variables

Read by runQwenServe / Express middleware

Env Effect
QWEN_SERVER_TOKEN Bearer token; trimmed at boot.
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_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 Overrides getCurrentGeminiMdFilename() through BridgeOptions.contextFilename.
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.
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 token and session-bound client id are still required.
promptDeadlineMs Prompt wallclock limit.
writerIdleTimeoutMs SSE writer idle timeout.
channelIdleTimeoutMs How long to keep the ACP child warm after the last session closes.
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.
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.
contextFilename Overrides getCurrentGeminiMdFilename().
channelIdleTimeoutMs How long to keep the ACP child alive after the last session closes, in ms; default 0.

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 5 * 60_000 Per-permission request wallclock.
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