add codex oauth option#183
Conversation
Signed-off-by: sallyom <somalley@redhat.com>
WalkthroughThis PR adds Codex OAuth support to the Claw operator, enabling deployments to authenticate with ChatGPT using Codex CLI credentials. A new ChangesCodex OAuth Support
Sequence DiagramsequenceDiagram
participant Reconcile as Reconcile Loop
participant Validate as validateCodexOAuthConfig
participant Inject as injectCodexOAuthConfig
participant Deploy as configureClawDeploymentForCodexOAuth
participant InitScript as merge.js InitScript
Reconcile->>Validate: validate Secret + auth.json
Validate->>Validate: extract tokens, verify auth_mode
Reconcile->>Inject: inject OAuth config to operator.json
Inject->>Inject: set auth profiles, plugin allowlist, agent models
Reconcile->>Deploy: mutate Deployment for OAuth
Deploy->>Deploy: add secret mount + profile ID env var
Deploy->>InitScript: secret mounted to init container
InitScript->>InitScript: decode auth.json, import profiles to auth-profiles.json
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/assets/manifests/claw/configmap.yaml`:
- Around line 134-161: The importCodexOAuthProfiles function currently only
upserts store.profiles[codexOAuthProfileId] and never removes operator-managed
Codex entries, so disabling spec.codexOAuth or changing profileID leaves stale
credentials in each agent's auth-profiles.json; update importCodexOAuthProfiles
(and the logic that runs when codexOAuthPath is missing) to iterate
configuredAgentIds(config) and, for each agent storePath, remove the old
operator-managed profile key(s) (at minimum the previous codexOAuthProfileId and
the current codexOAuthProfileId when spec.codexOAuth is absent or profileID
changed), then write back the store (or delete the file if profiles becomes
empty) preserving file permissions (mode 0o600); use the existing symbols
codexOAuthProfileId, configuredAgentIds, and storePath to locate and prune
entries.
In `@internal/controller/claw_proxy_test.go`:
- Around line 698-707: The test "should order exact matches before suffix
matches" in TestGenerateProxyConfig expects a hard-coded domain order that was
changed by adding a new exact-match route ("chatgpt.com"); update the expected
domain list in that test (the assertion that compares generated routes from
generateProxyConfig) to include "chatgpt.com" among the exact-match entries (so
it appears before suffix matches like "clawhub.ai") and adjust the assertion
accordingly so exact matches are still asserted to come before suffix matches.
In `@README.md`:
- Around line 199-225: The example manifest only sets spec.codexOAuth which will
replace the resource's spec and drop any existing spec.credentials; update the
README example to show a safe approach that preserves existing
credentials—either include existing spec.credentials alongside spec.codexOAuth
in the sample Claw manifest (so the apply is additive) or demonstrate using a
merge/patch operation (e.g., oc/kubectl patch or a strategic merge) to add
spec.codexOAuth without overwriting spec.credentials; reference the Claw
resource and the spec.codexOAuth and spec.credentials fields in the instructions
so users know which keys must be preserved.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Enterprise
Run ID: e32d4951-53ee-480a-8327-03e226c54750
📒 Files selected for processing (11)
README.mdapi/v1alpha1/claw_types.goapi/v1alpha1/zz_generated.deepcopy.goconfig/crd/bases/claw.sandbox.redhat.com_claws.yamldocs/user-guide.mdinternal/assets/manifests/claw/configmap.yamlinternal/controller/claw_codex_oauth.gointernal/controller/claw_codex_oauth_test.gointernal/controller/claw_proxy.gointernal/controller/claw_proxy_test.gointernal/controller/claw_resource_controller.go
📜 Review details
🧰 Additional context used
📓 Path-based instructions (7)
internal/controller/**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
Set owner references on all created resources via
controllerutil.SetControllerReference
Files:
internal/controller/claw_proxy.gointernal/controller/claw_proxy_test.gointernal/controller/claw_resource_controller.gointernal/controller/claw_codex_oauth.gointernal/controller/claw_codex_oauth_test.go
**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.go: Include Go license header fromhack/boilerplate.go.txttemplate in all Go source files
Run golangci-lint viamake lintand usemake lint-fixfor auto-fixing, with.golangci.ymlconfiguration includinglllanddupllinters enabled
Usemake fmt(go fmt) andmake vet(go vet) for code formatting and vetting before committing
Files:
internal/controller/claw_proxy.gointernal/controller/claw_proxy_test.goapi/v1alpha1/claw_types.goapi/v1alpha1/zz_generated.deepcopy.gointernal/controller/claw_resource_controller.gointernal/controller/claw_codex_oauth.gointernal/controller/claw_codex_oauth_test.go
**
⚙️ CodeRabbit configuration file
-Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity.
Files:
internal/controller/claw_proxy.godocs/user-guide.mdinternal/controller/claw_proxy_test.goapi/v1alpha1/claw_types.goapi/v1alpha1/zz_generated.deepcopy.goREADME.mdconfig/crd/bases/claw.sandbox.redhat.com_claws.yamlinternal/controller/claw_resource_controller.gointernal/controller/claw_codex_oauth.gointernal/assets/manifests/claw/configmap.yamlinternal/controller/claw_codex_oauth_test.go
**/*_test.go
📄 CodeRabbit inference engine (CLAUDE.md)
**/*_test.go: Userequireassertion from testify for fatal setup errors in tests, andassertfor value comparisons
Structure tests withTest*function names, uset.Run()for subtests,t.Cleanup()for cleanup, and implement table-driven test patterns
UsewaitFor(t, timeout, interval, condition, message)helper for async test assertions with 10s timeout and 250ms poll interval
Create separate test files per resource type (e.g.,claw_configmap_test.go,claw_credentials_test.go)
Files:
internal/controller/claw_proxy_test.gointernal/controller/claw_codex_oauth_test.go
api/v1alpha1/**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
Modify API types in
api/v1alpha1/and runmake manifeststo regenerate CRD YAML inconfig/crd/bases/andmake generateto regenerate zz_generated.deepcopy.go
Files:
api/v1alpha1/claw_types.goapi/v1alpha1/zz_generated.deepcopy.go
internal/controller/**/*_controller.go
📄 CodeRabbit inference engine (CLAUDE.md)
Apply RBAC authorization via
// +kubebuilder:rbac:...markers on reconciler methods in the controller
Files:
internal/controller/claw_resource_controller.go
internal/assets/manifests/**/*.{yaml,yml}
📄 CodeRabbit inference engine (CLAUDE.md)
internal/assets/manifests/**/*.{yaml,yml}: Configure pod security with non-root user (uid 65532), restricted seccomp profile, and drop all Linux capabilities
SetreadOnlyRootFilesystem: trueon proxy andwait-for-proxycontainers, but not on init-config or gateway containers which require writable paths for Node.js and AI tools
Files:
internal/assets/manifests/claw/configmap.yaml
🧠 Learnings (5)
📚 Learning: 2026-06-01T02:00:32.268Z
Learnt from: CR
Repo: codeready-toolchain/claw-operator PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-01T02:00:32.268Z
Learning: Use the MITM proxy to inject credentials transparently, with known providers getting auto-inferred defaults from the centralized `knownProviders` registry in `claw_providers.go`
Applied to files:
internal/controller/claw_proxy.godocs/user-guide.md
📚 Learning: 2026-06-01T02:00:32.268Z
Learnt from: CR
Repo: codeready-toolchain/claw-operator PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-01T02:00:32.268Z
Learning: Applies to api/v1alpha1/**/*.go : Modify API types in `api/v1alpha1/` and run `make manifests` to regenerate CRD YAML in `config/crd/bases/` and `make generate` to regenerate zz_generated.deepcopy.go
Applied to files:
api/v1alpha1/zz_generated.deepcopy.goREADME.md
📚 Learning: 2026-06-01T02:00:32.268Z
Learnt from: CR
Repo: codeready-toolchain/claw-operator PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-01T02:00:32.268Z
Learning: Manage Kubernetes resources via Kustomize with server-side apply in the unified `ClawResourceReconciler` controller managing all resources through two components: `claw/` and `claw-proxy/`
Applied to files:
internal/controller/claw_resource_controller.gointernal/controller/claw_codex_oauth.go
📚 Learning: 2026-05-27T15:29:16.197Z
Learnt from: alexeykazakov
Repo: codeready-toolchain/claw-operator PR: 149
File: internal/assets/manifests/claw/kustomization.yaml:19-19
Timestamp: 2026-05-27T15:29:16.197Z
Learning: In this repo’s claw Kustomize YAML manifests, when an image tag for ghcr.io (specifically ghcr.io/openclaw/openclaw) is being verified via `crane manifest` in an unauthenticated environment, a `not found` result can be caused by missing registry authentication (false positives). Do not flag the tag as missing based solely on that `crane manifest` output; require an authenticated verification step (e.g., GHCR login/token) or another independent check before concluding the tag is absent.
Applied to files:
internal/assets/manifests/claw/configmap.yaml
📚 Learning: 2026-06-01T02:00:32.268Z
Learnt from: CR
Repo: codeready-toolchain/claw-operator PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-01T02:00:32.268Z
Learning: Applies to **/*_test.go : Create separate test files per resource type (e.g., `claw_configmap_test.go`, `claw_credentials_test.go`)
Applied to files:
internal/controller/claw_codex_oauth_test.go
🔇 Additional comments (1)
internal/controller/claw_resource_controller.go (1)
1675-1677: ⚡ Quick winClarify Secret-rotation rollout: Codex OAuth
auth.jsonis (or isn’t) covered by pod-template secret-version stampingThe operator already stamps pod-template annotations with Secret
resourceVersions to trigger a rolling update (r.stampSecretVersionAnnotation(ctx, objects, instance)), and the secret-to-reconcile mapping includesinstance.Spec.CodexOAuth.SecretRef.Name == secretName(lines 1675-1677 ininternal/controller/claw_resource_controller.go). This directly undercuts the claim that secret rotation would leaveauth-profiles.jsonstale until an unrelated rollout.That said, the actual behavior depends on whether
stampSecretVersionAnnotationincludes the Codex OAuth Secret in the set of Secrets it stamps (needs the implementation ininternal/controller/claw_proxy.go). If it does, Secret rotation will restart the gateway pod template and theinit-configimport will run again; if it doesn’t, the original concern still applies.
| function importCodexOAuthProfiles(config) { | ||
| if (!fs.existsSync(codexOAuthPath)) return; | ||
| const credential = codexOAuthCredentialFromAuthJson(fs.readFileSync(codexOAuthPath, "utf8")); | ||
| for (const agentId of configuredAgentIds(config)) { | ||
| const storePath = path.join(pvcDir, "agents", agentId, "agent", "auth-profiles.json"); | ||
| let store = { version: 1, profiles: {} }; | ||
| if (fs.existsSync(storePath)) { | ||
| try { | ||
| const existing = JSON.parse(fs.readFileSync(storePath, "utf8")); | ||
| if (existing && typeof existing === "object" && !Array.isArray(existing)) { | ||
| store = { | ||
| version: existing.version || 1, | ||
| profiles: existing.profiles && typeof existing.profiles === "object" && !Array.isArray(existing.profiles) | ||
| ? existing.profiles | ||
| : {}, | ||
| }; | ||
| } | ||
| } catch { | ||
| console.warn(`[init-config] existing ${storePath} is invalid JSON; replacing it`); | ||
| } | ||
| } | ||
| store.version = 1; | ||
| store.profiles[codexOAuthProfileId] = credential; | ||
| fs.mkdirSync(path.dirname(storePath), { recursive: true }); | ||
| fs.writeFileSync(storePath, JSON.stringify(store, null, 2), { mode: 0o600 }); | ||
| fs.chmodSync(storePath, 0o600); | ||
| console.log(`[init-config] imported Codex OAuth profile for ${agentId}`); | ||
| } |
There was a problem hiding this comment.
Disabling Codex OAuth does not remove the persisted credential.
This path only upserts store.profiles[codexOAuthProfileId]. When spec.codexOAuth is removed, or when profileID changes, the previous ChatGPT refresh token stays under /home/node/.openclaw/agents/*/agent/auth-profiles.json indefinitely because nothing prunes operator-managed Codex profiles. That means turning the feature off does not actually remove the credential from the PVC.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/assets/manifests/claw/configmap.yaml` around lines 134 - 161, The
importCodexOAuthProfiles function currently only upserts
store.profiles[codexOAuthProfileId] and never removes operator-managed Codex
entries, so disabling spec.codexOAuth or changing profileID leaves stale
credentials in each agent's auth-profiles.json; update importCodexOAuthProfiles
(and the logic that runs when codexOAuthPath is missing) to iterate
configuredAgentIds(config) and, for each agent storePath, remove the old
operator-managed profile key(s) (at minimum the previous codexOAuthProfileId and
the current codexOAuthProfileId when spec.codexOAuth is absent or profileID
changed), then write back the store (or delete the file if profiles becomes
empty) preserving file permissions (mode 0o600); use the existing symbols
codexOAuthProfileId, configuredAgentIds, and storePath to locate and prune
entries.
| t.Run("should include chatgpt.com with backend-api path restriction", func(t *testing.T) { | ||
| data, err := generateProxyConfig(nil, nil, nil) | ||
| require.NoError(t, err) | ||
|
|
||
| var cfg proxyConfig | ||
| require.NoError(t, json.Unmarshal(data, &cfg)) | ||
| route := findRouteByDomain(t, cfg.Routes, "chatgpt.com") | ||
| assert.Equal(t, "none", route.Injector) | ||
| assert.Equal(t, []string{"/backend-api/"}, route.AllowedPaths) | ||
| }) |
There was a problem hiding this comment.
Update the hard-coded route-order test as part of this change.
Line 698 adds a new exact-match builtin route, but TestGenerateProxyConfig/should order exact matches before suffix matches still asserts the pre-change domain list. That test now fails because chatgpt.com sorts before clawhub.ai.
Minimal fix
expectedDomains := []string{
"api.example.com",
+ "chatgpt.com",
"clawhub.ai",
"codeload.github.com",
"github.com",
"openrouter.ai",
"raw.githubusercontent.com",🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/controller/claw_proxy_test.go` around lines 698 - 707, The test
"should order exact matches before suffix matches" in TestGenerateProxyConfig
expects a hard-coded domain order that was changed by adding a new exact-match
route ("chatgpt.com"); update the expected domain list in that test (the
assertion that compares generated routes from generateProxyConfig) to include
"chatgpt.com" among the exact-match entries (so it appears before suffix matches
like "clawhub.ai") and adjust the assertion accordingly so exact matches are
still asserted to come before suffix matches.
| #### OpenAI Codex OAuth | ||
|
|
||
| After logging in with Codex CLI, import ChatGPT OAuth from `~/.codex/auth.json`: | ||
|
|
||
| ```sh | ||
| oc create secret generic codex-oauth \ | ||
| --from-file=auth.json=$HOME/.codex/auth.json \ | ||
| -n $NS | ||
| ``` | ||
|
|
||
| ```sh | ||
| oc apply -f - <<EOF | ||
| apiVersion: claw.sandbox.redhat.com/v1alpha1 | ||
| kind: Claw | ||
| metadata: | ||
| name: instance | ||
| namespace: $NS | ||
| spec: | ||
| codexOAuth: | ||
| secretRef: | ||
| name: codex-oauth | ||
| key: auth.json | ||
| model: gpt-5.5 | ||
| EOF | ||
| ``` | ||
|
|
||
| For known providers (`google`, `anthropic`, `openai`, `xai`), the operator infers `type` and `domain` automatically. Codex OAuth is configured separately with `spec.codexOAuth`. For other LLM providers, messaging channels, MCP servers, and more, see the [User Guide](docs/user-guide.md). |
There was a problem hiding this comment.
Preserve existing credentials in this sample.
This section says Codex OAuth is configured separately, but the manifest only sets spec.codexOAuth. Applying it to an instance that already has spec.credentials removes those entries, so the documented “configure OpenAI separately” flow breaks on the next oc apply. The same caveat applies to the new Codex OAuth example in docs/user-guide.md.
Safer example
spec:
+ credentials:
+ - name: openai
+ provider: openai
+ secretRef:
+ - name: openai-api-key
+ key: api-key
codexOAuth:
secretRef:
name: codex-oauth
key: auth.json📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| #### OpenAI Codex OAuth | |
| After logging in with Codex CLI, import ChatGPT OAuth from `~/.codex/auth.json`: | |
| ```sh | |
| oc create secret generic codex-oauth \ | |
| --from-file=auth.json=$HOME/.codex/auth.json \ | |
| -n $NS | |
| ``` | |
| ```sh | |
| oc apply -f - <<EOF | |
| apiVersion: claw.sandbox.redhat.com/v1alpha1 | |
| kind: Claw | |
| metadata: | |
| name: instance | |
| namespace: $NS | |
| spec: | |
| codexOAuth: | |
| secretRef: | |
| name: codex-oauth | |
| key: auth.json | |
| model: gpt-5.5 | |
| EOF | |
| ``` | |
| For known providers (`google`, `anthropic`, `openai`, `xai`), the operator infers `type` and `domain` automatically. Codex OAuth is configured separately with `spec.codexOAuth`. For other LLM providers, messaging channels, MCP servers, and more, see the [User Guide](docs/user-guide.md). | |
| spec: | |
| credentials: | |
| - name: openai | |
| provider: openai | |
| secretRef: | |
| - name: openai-api-key | |
| key: api-key | |
| codexOAuth: | |
| secretRef: | |
| name: codex-oauth | |
| key: auth.json |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` around lines 199 - 225, The example manifest only sets
spec.codexOAuth which will replace the resource's spec and drop any existing
spec.credentials; update the README example to show a safe approach that
preserves existing credentials—either include existing spec.credentials
alongside spec.codexOAuth in the sample Claw manifest (so the apply is additive)
or demonstrate using a merge/patch operation (e.g., oc/kubectl patch or a
strategic merge) to add spec.codexOAuth without overwriting spec.credentials;
reference the Claw resource and the spec.codexOAuth and spec.credentials fields
in the instructions so users know which keys must be preserved.
|
Thank you for the PR! However there is a concern with this approach. It keeps the real codex secret in the gateway. Here is a PR with an alternative approach: #184 |
|
closing in favor of #184 ! |
This adds codex oauth option. Most who use OpenAI models use OAuth.
Summary by CodeRabbit
New Features
Documentation