# Codex plugin pinned old

```yaml qa-scenario
id: codex-plugin-pinned-old
title: Codex plugin pinned old
surface: runtime
runtimeParityTier: standard
coverage:
  primary:
    - runtime.codex-plugin.version
objective: Verify a Codex plugin pinned behind the OpenClaw host version fails closed with a precise update remediation.
successCriteria:
  - The lifecycle fixture detects the plugin version is older than the host version.
  - The failure remediation points to openclaw plugins update codex or unpinning the plugin, then rerunning doctor.
  - The remediation string is asserted literally by the Phase 3 test.
docsRefs:
  - docs/cli/plugins.md
  - docs/cli/update.md
codeRefs:
  - extensions/qa-lab/src/codex-plugin.fixture.ts
  - extensions/qa-lab/src/codex-plugin-lifecycle.test.ts
execution:
  kind: flow
  summary: Exercise the lifecycle fixture for pinned-old Codex plugin mismatch.
  config:
    pluginVersion: 2026.5.19
    hostVersion: 2026.5.21
    pluginRelation: older
    remediation: Codex plugin version 2026.5.19 is older than OpenClaw 2026.5.21. Run "openclaw plugins update codex" or unpin codex, then rerun "openclaw doctor --fix".
```

```yaml qa-flow
steps:
  - name: validates pinned-old remediation
    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-old-"))
      - try:
          actions:
            - call: plugin.seedCodexPluginAt
              args:
                - expr: config.pluginVersion
                - ref: tmpRoot
            - call: auth.seedAuthProfiles
              args:
                - oauth-only
                - ref: tmpRoot
            - set: result
              value:
                expr: "plugin.evaluateCodexPluginLifecycle({ plugin: await plugin.snapshotCodexPluginState(tmpRoot), auth: await auth.snapshotAuthProfiles(tmpRoot), hostVersion: config.hostVersion })"
            - assert:
                expr: "result.status === 'blocked'"
                message:
                  expr: "`expected blocked pinned-old plugin, got ${JSON.stringify(result)}`"
            - assert:
                expr: "result.remediation === config.remediation"
                message: pinned-old remediation drifted
          finally:
            - call: fs.rm
              args:
                - ref: tmpRoot
                - recursive: true
                  force: true
      - assert:
          expr: "config.pluginRelation === 'older'"
          message: "expected plugin version to be older than host"
    detailsExpr: "`plugin=${config.pluginVersion} host=${config.hostVersion} status=${result.status}`"
```
