# How to Run Qwen Code in Headless CI Environments Without OAuth Browser Flow

> Learn how to run Qwen Code in headless CI environments without OAuth browser flow. Discover authentication methods like cached tokens, API keys, and device-code flows for seamless integration.

- Repository: [Qwen/qwen-code](https://github.com/qwenlm/qwen-code)
- Tags: how-to-guide
- Published: 2026-02-19

---

**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`](https://github.com/QwenLM/qwen-code/blob/main/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`](https://github.com/QwenLM/qwen-code/blob/main/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`](https://github.com/QwenLM/qwen-code/blob/main/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`](https://github.com/QwenLM/qwen-code/blob/main/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:

```json
{
  "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`](https://github.com/QwenLM/qwen-code/blob/main/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`](https://github.com/QwenLM/qwen-code/blob/main/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`](https://github.com/QwenLM/qwen-code/blob/main/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`](https://github.com/QwenLM/qwen-code/blob/main/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:

```yaml

# .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:

```typescript
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:

```bash

# 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`](https://github.com/QwenLM/qwen-code/blob/main/sharedTokenManager.ts) reads this file and authenticates without browser interaction.

## Summary

- **CI Detection**: The [`is-in-ci.ts`](https://github.com/QwenLM/qwen-code/blob/main/is-in-ci.ts) utility checks `process.env.CI` to disable browser-dependent workflows.
- **Browser Guard**: [`secure-browser-launcher.ts`](https://github.com/QwenLM/qwen-code/blob/main/secure-browser-launcher.ts) prevents `open()` 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.ts`](https://github.com/QwenLM/qwen-code/blob/main/sharedTokenManager.ts) handles token expiration silently using stored refresh tokens.
- **Model Integration**: The `ModelRegistry` resolves 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`](https://github.com/QwenLM/qwen-code/blob/main/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`](https://github.com/QwenLM/qwen-code/blob/main/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`](https://github.com/QwenLM/qwen-code/blob/main/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`](https://github.com/QwenLM/qwen-code/blob/main/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.