Runtime overview
Asymptote 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. Asymptote 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. Asymptote’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-Asymptote hooks in the same event group survive, and a re-install replaces Asymptote’s entries instead of adding a second copy. Asymptote recognizes its own entries by the --platform qwen argument in the hook command.
Registered events
Asymptote 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 Asymptote 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.
Asymptote 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
Asymptote 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 Asymptote does not otherwise retain.
Discovery and status
Asymptote 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 Asymptote’s reports
disabled rather than enabled, because Asymptote 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. Asymptote’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 Asymptote 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. Asymptote 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. Asymptote does not synthesize an approval from a pre-tool notification for this runtime.
Asymptote’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. Asymptote 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 usage is not normalized. Qwen’s
Stoppayload carriescontext_usage,context_limit, andinput_tokens, which are session-level context counters rather than per-call usage. They are preserved underraw.qwenbut not normalized intogen_ai.usage, so Qwen Code activity does not appear inbeacon token-usagereports or the dashboard token view. - A policy deny is honored in one of two phases. The seam runs in both the
pre-toolandpermission-requesthooks, and Asymptote 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 Asymptote 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. Asymptote’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 Asymptote discovers and configures each runtime.

