Runtime overview
Beacon supports Qwen Code through native hook payloads sent tobeacon-hooks.
Qwen Code is a Gemini CLI fork, but it does not carry Gemini’s OpenTelemetry export, so there is no OTLP path for this runtime. Hooks are the only collection path. Beacon merges a managed hook block into Qwen’s own settings.json, the same way the Claude Code and Factory Droid installers do.
--harness qwen-code is also accepted. qwen-cli is not, because the product is Qwen Code.
Prerequisites
Before enabling Qwen Code telemetry, make sure:- Qwen Code is installed and available as the
qwenexecutable, or has a local config directory at~/.qwen. beacon endpoint installhas run so shared endpoint config and runtime log paths exist.- On Windows,
bashis onPATH. Beacon’s hooks declareshell: bash. See Hook shell. - Project-level hooks are only used in a folder Qwen Code trusts. See Known gaps.
- Qwen Code is restarted after hook install or removal so new sessions load the updated settings.
Collection path
Qwen Code reads settings from two locations, with project settings taking precedence over user settings:
User level is the default. A project-level install only takes effect once Qwen trusts the folder, and
beacon endpoint discover reports on the user path only, so a project-level install does not appear in discovery output.
settings.json also holds the user’s own configuration, such as model, theme, MCP servers, and their own hooks. Install merges into that file rather than rewriting it: unrelated top-level keys are preserved, non-Beacon hooks in the same event group survive, and a re-install replaces Beacon’s entries instead of adding a second copy. Beacon recognizes its own entries by the --platform qwen argument in the hook command.
Registered events
Beacon registers every Qwen event that carries endpoint signal:
A failed tool is a separate event in Qwen, so
PostToolUseFailure is registered alongside PostToolUse. PermissionRequest is what makes an approval prompt observable. Events with no timeout listed use Qwen’s own default of 60000 ms.
Hook shell
Every hook Beacon writes for Qwen carriesshell: bash. Qwen accepts two values, bash and powershell, and only uses bash when the hook configuration asks for it. Claude Code, by contrast, runs hook commands through Git Bash on Windows regardless.
Beacon quotes its hook command with POSIX single quotes, which is what a Windows path needs. If shell were left unset on Windows, a Node-hosted runtime would fall back to cmd.exe, where those quotes are literal characters and every hook fails to start while the install still reports success. On macOS and Linux, bash names the shell that would have run anyway.
Events that are not collected
Beacon does not register Qwen’s fire-and-forget events:MessageDisplay, StopFailure, PostCompact, SessionDelete, and the todo pair. MessageDisplay alone fires roughly every 200 ms for the length of every reply, and it carries assistant text that Beacon does not otherwise retain.
Discovery and status
Beacon detects Qwen Code through theqwen executable, and treats the ~/.qwen config directory as an additional signal. Qwen Code installs through npm, so a version manager, a shell alias, or a per-project install can hide the binary from the endpoint process’s PATH while ~/.qwen is still present. The OpenCode, Cursor, Pi, and Hermes probes use the same fallback.
Show Qwen Code discovery state
qwen_code harness with capability hooks:
A settings file with hooks that are not Beacon’s reports
disabled rather than enabled, because Beacon is not collecting from them. Invalid JSON reports misconfigured rather than missing, because the file is there and install will refuse to write to it.
disableAllHooks is Qwen’s own kill switch. Beacon’s hooks can be present in the file and still never run. Fix that in Qwen’s settings, not by re-running the installer.
Show Qwen Code hook status
Install or configuration support
beacon endpoint install prepares shared endpoint config and runtime log paths. Qwen Code hooks are installed separately, because Qwen loads them from the user’s own profile rather than from a machine-wide location:
beacon endpoint install does not configure Qwen Code. Passing --harness qwen to it fails with a message pointing at beacon endpoint hooks install.
Telemetry coverage
Harness names are matched against a closed set of spellings rather than by substring, because every Qwen model id starts with the same four letters as the harness:
qwen3-coder-plus, qwen-max, qwen-turbo. A substring rule would report any event carrying a Qwen model string as a Qwen Code session.
Both qwen and qwen_code arrive in practice. The hook path installs with --platform qwen, while an OTLP resource attribute carries whatever the runtime calls itself. Pinning both keeps one session from being recorded under two names.
Tool taxonomy
Qwen sends the Claude Code payload shape but not Claude’s tool names. Its built-ins are snake_case ids inherited from the Gemini CLI it forked, and Beacon matches them against a closed set:
Without this mapping the generic classifier is wrong in both directions.
write_file, edit, replace, and notebook_edit fall through to tool.invoked because the default only recognizes Claude’s Write, Edit, and MultiEdit, and list_directory, glob, and grep_search are not recorded as reads. Both are misclassifications rather than gaps: the event is present, the action is wrong, and nothing looks broken.
read_many_files, search_file_content, and replace are Gemini CLI spellings that Qwen Code renamed. They are kept because a hook installed against one version can receive payloads from another after an upgrade.
The set is closed rather than substring-matched so an MCP tool whose name contains edit or glob is not classified as a Qwen built-in. Qwen’s non-filesystem built-ins (web_fetch, web_search, todo_write, save_memory, task, skill) stay on the shared path, where the MCP rule and the tool.invoked fallback already give the right answer.
A failed tool is classified as a failure before its name is consulted, so a write_file that hit EACCES is recorded as tool.failed rather than a successful file.modified, and it does not go down the diff path. Beacon reads three independent failure signals, since any of them can arrive alone: the PostToolUseFailure event name, a non-empty error, and Qwen’s is_interrupt flag.
Approvals and enforcement
Approval telemetry comes from Qwen’sPermissionRequest event and is recorded as observed, because an operator was actually asked. Beacon does not synthesize an approval from a pre-tool notification for this runtime.
Beacon’s PreToolUse hook answers with an empty object. Qwen’s PreToolUse contract reads a decision from hookSpecificOutput.permissionDecision, where allow means run the tool without the usual approval prompt. A hook that answered allow would disarm the user’s own permission prompts for every tool call. An empty object carries no decision, so Qwen’s normal permission flow runs untouched.
Enforcement stays behind the optional, off-by-default policy provider seam (BEACON_POLICY_PROVIDER), which is inert unless it names an executable and fails open on any error. When a provider denies a call, Qwen uses Claude Code’s deny shape: hookSpecificOutput.permissionDecision with a permissionDecisionReason.
Data handling
Qwen Code content is handled like every other runtime. Prompts, tool arguments, commands, paths, and diffs are retained in local or customer-controlled logs, with local secret redaction and per-string limits applied before writing, and a hash and byte count on each content-bearing event. Events over the 64 KiB limit drop raw and retained content, preserve stable metadata, and setfield_truncated.
Qwen carries signal with no endpoint-schema field of its own: permission_mode on every tool event, source on SessionStart, and stop_hook_active plus the context_usage / context_limit / input_tokens trio on Stop. Beacon preserves the verbatim payload under raw.qwen rather than inventing schema fields for one runtime. It travels the event’s normal path through sanitization, so raw is secret-redacted and string-limited like every other field, and it is the first thing dropped when an event exceeds the size ceiling.
Known gaps
- No OTLP path. Qwen Code is a Gemini CLI fork without Gemini’s OpenTelemetry export, so there is no collector endpoint to configure. Hooks are the only collection path, and
beacon endpoint install --harness qwensays so rather than appearing to succeed. - Token spend is not collected, and cannot be. Qwen’s
Stoppayload carries no per-call token counts. Itsinput_tokensis the prompt for that turn, which in a multi-turn session already contains every prior turn, so summing it intogen_ai.usagewould inflate a session’s total by roughly the square of its length. Qwen Code therefore contributes no rows to the spend totals inbeacon token-usage. - Context utilization is collected. The same numbers describe how full the window was, which is a level rather than an amount, so they normalize into
gen_ai.contextinstead:input_tokensbecomesgen_ai.context.used_tokensandcontext_limitbecomesgen_ai.context.limit_tokens. Qwen Code appears in the context utilization section ofbeacon token-usage, measured against the window it reported rather than a static model table.context_usageis the ratio of the two and stays inraw.qwenrather than being stored a second time in different units. - A policy deny is honored in one of two phases. The seam runs in both the
pre-toolandpermission-requesthooks, and Beacon returns Claude Code’s deny shape, which Qwen’sPreToolUsecontract honors. Qwen’sPermissionRequestevent reads a differently shapedhookSpecificOutput.decisionobject, so a deny raised from that phase is not honored. Because the seam fails open, the cost is a deny that does not take effect on one phase, never a tool call blocked for the wrong reason. - Assistant text is not collected.
MessageDisplayis not registered, so Beacon records no assistant output or reasoning for Qwen Code. See Events that are not collected. - Project-level installs need a trusted folder. Qwen gates project hooks behind trusted-folder status, so a user-level install is the one that works without further interaction. Grok Build has the same caveat.
- Payload fixtures are built from documentation. Beacon’s Qwen fixtures are constructed from Qwen Code’s published hooks documentation rather than captured from a running install. The documentation specifies the envelope and per-event fields precisely, so the shapes match the documented contract, but they are not evidence that a shipped build sends exactly this.
Deployment notes
Settings are read when Qwen Code starts, so an install taken while Qwen is running takes effect on the next launch. Restart Qwen after installing or removing hooks. Confirm discovery sees the runtime, then generate one Qwen Code event and check the log:- Check the selected level, user or project.
- Confirm
settings.jsoncontains commands with--platform qwen. - Confirm
disableAllHooksis not set. - Confirm hook timeouts are millisecond values.
- Trust the folder for a project-level install.
- Restart Qwen so new sessions pick up the updated settings.
Related
Supported agent harnesses
Return to the runtime support overview.
Agent harness integration model
See how Beacon discovers and configures each runtime.