How to Run Qwen Code in Headless CI Environments Without OAuth Browser Flow
Qwen Code detects CI environments via environment variables, suppresses browser launches, and authenticates using pre-cached OAuth tokens, static API keys, or console-based device-code flows.
The QwenLM/qwen-code repository is designed to operate seamlessly in non-interactive environments such as GitHub Actions, Docker containers, and headless servers. Instead of relying on interactive browser-based OAuth flows, the CLI and SDK implement a CI-first authentication architecture that prioritizes automated credential detection and token-based authentication.
CI Detection and Browser Suppression
Qwen Code begins its headless workflow by detecting whether it is running in a continuous integration environment. In packages/cli/src/patches/is-in-ci.ts, the system checks for the presence of process.env.CI and other common CI environment variables to return a boolean flag.
This detection flag propagates to packages/core/src/utils/secure-browser-launcher.ts, which wraps all open(url) calls with a conditional guard: if (!isInCI()). When running in CI, this prevents any attempt to launch a graphical browser, eliminating the risk of hanging processes or failed automation scripts.
Headless Authentication Strategies
When browser interaction is impossible, Qwen Code falls back to three distinct authentication mechanisms implemented across the core utilities.
Pre-Cached OAuth Credentials
The sharedTokenManager.ts module in packages/core/src/qwen/ manages persistent authentication by reading from ${HOME}/.qwen/oauth_creds.json (path defined in packages/core/src/config/storage.ts). If this file contains a valid access_token and optional refresh_token, the system uses these credentials directly without user interaction.
The token file follows a simple JSON structure:
{
"access_token": "eyJhbGciOi...",
"refresh_token": "2Yg0kR..."
}
Static API Key Fallback
When no cached OAuth file exists, Qwen Code checks for the QWEN_API_KEY environment variable. This static credential bypasses the device-code flow entirely, allowing immediate authentication with Qwen servers. According to the integration documentation in docs/users/integration-github-action.md, this is the recommended approach for CI pipelines.
Device-Code Flow for Console-Only Access
If neither cached tokens nor API keys are available, but QWEN_OAUTH_CLIENT_ID is configured, the QwenOAuth2 class in packages/core/src/qwen/qwenOAuth2.ts initiates an OAuth device-code flow. This programmatic approach fetches a device_code, prints the user-code and verification URL to CI logs, and polls for the token—all without requiring a browser.
Token Lifecycle and Model Integration
Once authenticated, Qwen Code handles token maintenance automatically. The sharedTokenManager.ts module refreshes expired access tokens using stored refresh_token values, ensuring long-running CI jobs maintain connectivity without manual intervention.
The resolved token is injected into the generationConfig.apiKey field using the magic value QWEN_OAUTH_DYNAMIC_TOKEN. The ModelRegistry class in packages/core/src/models/modelRegistry.ts then maps this placeholder to DYNAMIC_QWEN_OAUTH_BASE_URL, completing the authentication chain for model inference requests.
Implementation Examples
Configuring GitHub Actions
For CI pipelines, set the QWEN_API_KEY environment variable using repository secrets. The CI environment variable is automatically detected, but explicit declaration ensures clarity:
# .github/workflows/qwen.yml
name: Qwen Code CI
on: [push]
jobs:
qwen:
runs-on: ubuntu-latest
env:
CI: true
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
steps:
- uses: actions/checkout@v3
- name: Install Qwen Code
run: npm install -g qwen
- name: Run Qwen prompt
run: qwen "Write a short summary of the repo"
Using the TypeScript SDK
When integrating via the SDK, specify the authentication type explicitly. The SDK automatically falls back to environment variables when cached credentials are absent:
import { Qwen, AuthType } from '@qwen/sdk-typescript';
const qwen = new Qwen({
auth: {
type: AuthType.QWEN_API_KEY, // Or AuthType.QWEN_OAUTH for cached tokens
},
});
await qwen.chat.completions.create({
model: 'qwen-turbo',
messages: [{ role: 'user', content: 'List the files in the repo' }],
});
Seeding Cached Credentials in CI
For scenarios requiring OAuth tokens rather than API keys, generate tokens locally and persist them to CI:
# Locally: generate and copy token content
cat ~/.qwen/oauth_creds.json
# In CI workflow: write the secret to the expected location
echo "$OAUTH_CREDS_JSON" > $HOME/.qwen/oauth_creds.json
When the job executes, sharedTokenManager.ts reads this file and authenticates without browser interaction.
Summary
- CI Detection: The
is-in-ci.tsutility checksprocess.env.CIto disable browser-dependent workflows. - Browser Guard:
secure-browser-launcher.tspreventsopen()calls in non-interactive environments. - Credential Priority: The system checks cached OAuth files first, then
QWEN_API_KEY, then falls back to device-code flows. - Automatic Refresh:
sharedTokenManager.tshandles token expiration silently using stored refresh tokens. - Model Integration: The
ModelRegistryresolves dynamic tokens to the correct base URL for API requests.
Frequently Asked Questions
How does Qwen Code know it is running in a CI environment?
Qwen Code examines environment variables through the isInCI() function in packages/cli/src/patches/is-in-ci.ts, which checks for process.env.CI and other common CI indicators like GITHUB_ACTIONS or GITLAB_CI. When detected, the system sets an internal flag that prevents browser launches and enables non-interactive authentication modes.
Can I use Qwen Code in Docker containers without a browser?
Yes. Containerized environments are treated as headless by default. Mount a volume containing ~/.qwen/oauth_creds.json or pass the QWEN_API_KEY environment variable to authenticate. The secure-browser-launcher.ts module ensures no browser calls are attempted regardless of the authentication method chosen.
What happens if my OAuth token expires during a long-running CI job?
The sharedTokenManager.ts module automatically refreshes access tokens using the stored refresh_token from ~/.qwen/oauth_creds.json. This process requires no user interaction and occurs before each API request if the current token is expired or nearing expiration.
Is the device-code flow secure for CI environments?
The device-code flow implemented in packages/core/src/qwen/qwenOAuth2.ts is designed for scenarios where API keys cannot be used but browser access is impossible. While functional, the static QWEN_API_KEY approach is recommended for CI because it eliminates polling overhead and potential timeout issues associated with device-code authorization.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →