# Codex plugin cold install

```yaml qa-scenario
id: codex-plugin-cold-install
title: Codex plugin cold install
surface: runtime
runtimeParityTier: standard
coverage:
  primary:
    - runtime.codex-plugin.lifecycle
  secondary:
    - runtime.doctor-repair
objective: Verify a clean home that needs the Codex runtime reports a clear missing-plugin remediation, installs through doctor repair, and retries through Codex OAuth instead of OpenAI API-key auth.
successCriteria:
  - Missing Codex plugin emits the exact remediation string asserted by the fixture test.
  - Doctor repair seeds the Codex plugin before retrying the agent turn.
  - The retry uses the openai OAuth profile and never routes through the openai API-key profile.
docsRefs:
  - docs/cli/doctor.md
  - docs/cli/plugins.md
  - docs/plugins/install-overrides.md
codeRefs:
  - extensions/qa-lab/src/codex-plugin.fixture.ts
  - extensions/qa-lab/src/auth-profile.fixture.ts
  - extensions/qa-lab/src/codex-plugin-lifecycle.test.ts
execution:
  kind: flow
  summary: Exercise the Codex lifecycle fixture for missing plugin repair and retry auth routing.
  config:
    remediation: Codex plugin is required for Codex runtime. Run "openclaw doctor --fix" to install @openclaw/codex, then retry.
```

```yaml qa-flow
steps:
  - name: validates cold-install repair routing
    actions:
      - set: auth
        value:
          expr: await qaImport("./auth-profile.fixture.js")
      - set: plugin
        value:
          expr: await qaImport("./codex-plugin.fixture.js")
      - set: tmpRoot
        value:
          expr: await fs.mkdtemp(path.join(env.gateway?.workspaceDir ?? "/tmp", "qa-codex-cold-"))
      - set: agentDir
        value:
          expr: path.join(tmpRoot, "agents", "qa", "agent")
      - try:
          actions:
            - call: plugin.seedCodexPluginAt
              args:
                - missing
                - ref: agentDir
            - call: auth.seedAuthProfiles
              args:
                - mixed
                - ref: agentDir
            - set: missing
              value:
                expr: "plugin.evaluateCodexPluginLifecycle({ plugin: await plugin.snapshotCodexPluginState(agentDir), auth: await auth.snapshotAuthProfiles(agentDir), hostVersion: plugin.CODEX_PLUGIN_CURRENT_VERSION })"
            - assert:
                expr: "missing.status === 'repair-required'"
                message:
                  expr: "`expected repair-required, got ${JSON.stringify(missing)}`"
            - assert:
                expr: "missing.remediation === config.remediation"
                message: missing Codex plugin remediation drifted
            - assert:
                expr: "missing.selectedAuthProfileId === auth.QA_CODEX_OAUTH_PROFILE_ID"
                message: missing-plugin repair must keep Codex OAuth selected
            - call: plugin.seedCodexPluginAt
              args:
                - current
                - ref: agentDir
            - set: repaired
              value:
                expr: "plugin.evaluateCodexPluginLifecycle({ plugin: await plugin.snapshotCodexPluginState(agentDir), auth: await auth.snapshotAuthProfiles(agentDir), hostVersion: plugin.CODEX_PLUGIN_CURRENT_VERSION })"
            - assert:
                expr: "repaired.status === 'ready' && repaired.tokenRoute === 'codex-oauth'"
                message:
                  expr: "`expected repaired Codex OAuth route, got ${JSON.stringify(repaired)}`"
          finally:
            - call: fs.rm
              args:
                - ref: tmpRoot
                - recursive: true
                  force: true
    detailsExpr: "`missing=${missing.status} repaired=${repaired.status} route=${repaired.tokenRoute}`"
```
