From 78dc32765a9ec09b79c7342337e84d20cf9de770 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 03:56:09 -0700 Subject: [PATCH 1/2] chore(deploy): retire clawdbot gateway chart and build paths Co-authored-by: flint Agent-Model: codex/gpt-6.1-sol/medium --- .github/workflows/deploy-dev.yml | 66 +- .github/workflows/deploy-staging.yml | 8 +- .github/workflows/tests.yml | 12 +- .../templates/agent-provisioning/pvc.yaml | 20 - .../templates/agents/clawdbot-deployment.yaml | 752 ------------------ .../templates/agents/clawdbot-service.yaml | 22 - .../agents/cloud-codex-deployment.yaml | 2 +- .../templates/configmaps/agent-configs.yaml | 115 --- .../templates/core/backend-deployment.yaml | 11 - .../templates/ops/ops-runner-deployment.yaml | 11 - .../templates/pvcs/clawdbot-config-pvc.yaml | 18 - .../pvcs/clawdbot-workspace-pvc.yaml | 18 - .../pvcs/filestore-storageclass.yaml | 12 - .../commonly/templates/secrets/api-keys.yaml | 9 - .../templates/secrets/local-secrets.yaml | 2 - k8s/helm/commonly/values-dev.yaml | 40 - k8s/helm/commonly/values-local.yaml | 14 +- k8s/helm/commonly/values-staging.yaml | 28 - k8s/helm/commonly/values.yaml | 58 +- scripts/verify-retired-gateway-chart.sh | 31 + 20 files changed, 44 insertions(+), 1205 deletions(-) delete mode 100644 k8s/helm/commonly/templates/agent-provisioning/pvc.yaml delete mode 100644 k8s/helm/commonly/templates/agents/clawdbot-deployment.yaml delete mode 100644 k8s/helm/commonly/templates/agents/clawdbot-service.yaml delete mode 100644 k8s/helm/commonly/templates/pvcs/clawdbot-config-pvc.yaml delete mode 100644 k8s/helm/commonly/templates/pvcs/clawdbot-workspace-pvc.yaml delete mode 100644 k8s/helm/commonly/templates/pvcs/filestore-storageclass.yaml create mode 100644 scripts/verify-retired-gateway-chart.sh diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index c4730e8c36..2141d63eaf 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -77,10 +77,8 @@ jobs: timeout-minutes: 30 steps: - - name: Checkout (with submodules for _external/clawdbot) + - name: Checkout uses: actions/checkout@v4 - with: - submodules: recursive - name: Compute image tag id: tag @@ -123,33 +121,6 @@ jobs: env: DEV_HELM_VALUES_PRIVATE: ${{ secrets.DEV_HELM_VALUES_PRIVATE }} - # clawdbot-gateway is parked (agents.clawdbot.enabled: false since #1051), - # and the chart then renders no gateway Deployment at all. Building it - # anyway cost 3.3 of the ~6.4 build minutes per deploy and pushed a - # ~1.1 GB image nothing ran: 709 such versions by 2026-10-14, about two - # thirds of the Artifact Registry bill. Ask the release itself, with the - # same three values files the upgrade uses, so the build returns the - # moment the flag does. "could not find template" is helm's answer for a - # template that rendered empty; any other helm error fails the job rather - # than reading as "disabled". - - name: Is clawdbot-gateway enabled in this release? - id: gateway - run: | - set -uo pipefail - if err=$(helm template commonly-dev k8s/helm/commonly \ - -f k8s/helm/commonly/values.yaml \ - -f k8s/helm/commonly/values-dev.yaml \ - -f "$RUNNER_TEMP/values-private.yaml" \ - --show-only templates/agents/clawdbot-deployment.yaml 2>&1 >/dev/null); then - echo "enabled=true" >> "$GITHUB_OUTPUT" - elif printf '%s' "$err" | grep -q 'could not find template'; then - echo "enabled=false" >> "$GITHUB_OUTPUT" - echo "clawdbot-gateway is disabled in this release; skipping its build and tag." - else - printf '%s\n' "$err" - exit 1 - fi - - name: Build + push backend image env: TAG: ${{ steps.tag.outputs.tag }} @@ -175,30 +146,6 @@ jobs: -t "$REPO:$TAG" docker push "$REPO:$TAG" - - name: Build + push clawdbot-gateway image - if: steps.gateway.outputs.enabled == 'true' - env: - TAG: ${{ steps.tag.outputs.tag }} - run: | - # Stage commonly-bundled-skills/ into the gateway build context - # so the Dockerfile's COPY commonly-bundled-skills /opt/... layer - # picks up the latest skill content. Sidesteps the kubectl-exec - # ARG_MAX limit that the runtime sync would otherwise hit when - # bundles carry sub-files (officecli specialized sub-skills, etc.). - rm -rf _external/clawdbot/commonly-bundled-skills - cp -r backend/commonly-bundled-skills _external/clawdbot/commonly-bundled-skills - - REPO=${REGION}-docker.pkg.dev/${PROJECT_ID}/${AR_REPO}/clawdbot-gateway - docker build _external/clawdbot \ - --build-arg OPENCLAW_EXTENSIONS=acpx \ - --build-arg OPENCLAW_INSTALL_GH_CLI=1 \ - --build-arg OPENCLAW_INSTALL_DOC_TOOLCHAIN=1 \ - -t "$REPO:$TAG" - docker push "$REPO:$TAG" - - # Cleanup so a re-run sees a deterministic build context. - rm -rf _external/clawdbot/commonly-bundled-skills - - name: Build + push commonly-bot image env: TAG: ${{ steps.tag.outputs.tag }} @@ -219,7 +166,7 @@ jobs: # false failures: `litellm` (CrashLoopBackOff, 561 restarts over 2d10h) # and `cloud-codex-cody` (Init:CrashLoopBackOff) never become ready, so # `--wait` burned the full 10m timeout and exited non-zero while - # backend/frontend/clawdbot-gateway/commonly-bot had all rolled out + # backend/frontend/commonly-bot had all rolled out # correctly minutes earlier. # # The error it surfaces — `client rate limiter Wait returned an error: @@ -236,26 +183,19 @@ jobs: - name: Helm upgrade commonly-dev env: TAG: ${{ steps.tag.outputs.tag }} - GATEWAY_ENABLED: ${{ steps.gateway.outputs.enabled }} run: | - # Point the gateway at this run's tag only when this run built it. - GATEWAY_TAG=() - if [ "$GATEWAY_ENABLED" = "true" ]; then - GATEWAY_TAG=(--set agents.clawdbot.image.tag="$TAG") - fi helm upgrade commonly-dev k8s/helm/commonly -n "$NAMESPACE" \ -f k8s/helm/commonly/values.yaml \ -f k8s/helm/commonly/values-dev.yaml \ -f "$RUNNER_TEMP/values-private.yaml" \ --set backend.image.tag="$TAG" \ --set frontend.image.tag="$TAG" \ - "${GATEWAY_TAG[@]}" \ --set agents.commonlyBot.image.tag="$TAG" \ --timeout 10m \ --burst-limit 200 \ --qps 100 - # Verify exactly what this job deploys: the four workloads whose image + # Verify exactly what this job deploys: the workloads whose image # tags it sets above. Scoping the check to them is the point — an # unrelated unhealthy workload is a real problem, but it is not THIS # job's failure, and conflating the two is what made the signal diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index fd0f035dcd..8078fc8d13 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -12,8 +12,8 @@ name: Deploy Staging # Modelled on deploy-dev.yml: same WIF auth, same secret names, same private # values overlay pattern. Differences: namespace/release commonly-staging, a # staging-only private-values secret, a single staging frontend variant per SHA -# (the shared prod tag is NOT pushed from staging — see the build step), no -# clawdbot-gateway (parked everywhere; staging never runs it), cancel-in-progress. +# (the shared prod tag is NOT pushed from staging — see the build step), and +# cancel-in-progress. # # DRAFT until the Thursday-night staging install lands — unmerged, this workflow # never runs. It goes live once commonly-staging exists (secrets, WI, DBs, tunnel). @@ -56,7 +56,6 @@ jobs: uses: actions/checkout@v4 with: ref: ${{ inputs.ref }} - submodules: recursive - name: Compute tag + full SHA of the checked-out ref id: tag @@ -105,8 +104,7 @@ jobs: # NOT serialize the writers). The promotion path that would justify a shared # artifact (deploy-dev promote_sha reusing a staging-verified build) does not # exist yet — reintroduce the shared-tag prod build only once it does, with - # enforced input parity. clawdbot-gateway is deliberately NOT built — it is - # parked everywhere (agents.clawdbot.enabled: false) and staging never runs it. + # enforced input parity. - name: Build + push backend image env: TAG: ${{ steps.tag.outputs.tag }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a32c39cec4..f2457c66db 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -147,13 +147,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - submodules: recursive - # The tool-contract guard compares the submodule pin against the - # merge base with origin/main to catch a ROLLBACK, which containment - # structurally cannot see. At the default depth 1 there is no - # origin/main and no shared history, so that check could not run at - # all (#2497). The guard now says so loudly instead of passing, and - # this is what lets it answer. + # Preserve merge-base history for release and source checks. fetch-depth: 0 - uses: actions/setup-node@v4 with: @@ -171,7 +165,6 @@ jobs: - run: cd commonly-mcp && npm ci --include=dev - run: cd cli && npm ci --include=dev - run: cd workers/agent-runtime && npm ci --include=dev - - run: npm run verify:moltbot-tools - run: npm run verify:litellm-patch-runner - run: npm run verify:readme-invariants # These node:test files lived only in the retired all-in-one job. @@ -330,6 +323,9 @@ jobs: sudo mv yq /usr/local/bin/yq yq --version + - name: Verify retired gateway stays absent + run: bash scripts/verify-retired-gateway-chart.sh + - name: Render dev chart run: | helm template commonly-dev k8s/helm/commonly \ diff --git a/k8s/helm/commonly/templates/agent-provisioning/pvc.yaml b/k8s/helm/commonly/templates/agent-provisioning/pvc.yaml deleted file mode 100644 index bb2224aea2..0000000000 --- a/k8s/helm/commonly/templates/agent-provisioning/pvc.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if and .Values.agentProvisioning.enabled .Values.agentProvisioning.createWorkspacePvc }} ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: clawdbot-workspace-pvc - namespace: {{ include "commonly.namespace" . }} - labels: - {{- include "commonly.labels" . | nindent 4 }} - app.kubernetes.io/component: agent-storage -spec: - accessModes: - - ReadWriteOnce - {{- if .Values.agentProvisioning.storageClass }} - storageClassName: {{ .Values.agentProvisioning.storageClass }} - {{- end }} - resources: - requests: - storage: {{ .Values.agentProvisioning.storageSize | default "10Gi" }} -{{- end }} diff --git a/k8s/helm/commonly/templates/agents/clawdbot-deployment.yaml b/k8s/helm/commonly/templates/agents/clawdbot-deployment.yaml deleted file mode 100644 index 16c454cd65..0000000000 --- a/k8s/helm/commonly/templates/agents/clawdbot-deployment.yaml +++ /dev/null @@ -1,752 +0,0 @@ -{{- if .Values.agents.clawdbot.enabled }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: clawdbot-gateway - namespace: {{ include "commonly.namespace" . }} - labels: - {{- include "commonly.labels" . | nindent 4 }} - app: clawdbot-gateway -spec: - replicas: 1 - {{- if .Values.agents.clawdbot.strategy }} - strategy: - {{- toYaml .Values.agents.clawdbot.strategy | nindent 4 }} - {{- else }} - strategy: - type: Recreate - {{- end }} - selector: - matchLabels: - {{- include "commonly.selectorLabels" . | nindent 6 }} - app: clawdbot-gateway - template: - metadata: - labels: - {{- include "commonly.selectorLabels" . | nindent 8 }} - app: clawdbot-gateway - spec: - {{- with .Values.agents.clawdbot.securityContext }} - securityContext: - {{- toYaml . | nindent 8 }} - {{- end }} - initContainers: - - name: clawdbot-auth-seed - image: "{{ .Values.agents.clawdbot.image.repository }}:{{ .Values.agents.clawdbot.image.tag }}" - imagePullPolicy: {{ .Values.agents.clawdbot.image.pullPolicy }} - command: - - /bin/sh - - -c - - | - node - <<'NODE' - const fs = require('fs'); - const path = require('path'); - const configPath = process.env.OPENCLAW_CONFIG_PATH || '/config/moltbot.json'; - const stateDir = process.env.OPENCLAW_STATE_DIR || '/state'; - const stateConfigPath = path.join(stateDir, 'moltbot.json'); - const geminiKey = (process.env.GEMINI_API_KEY || '').trim(); - const geminiKey2 = (process.env.GEMINI_API_KEY_2 || '').trim(); - const openrouterKey = (process.env.OPENROUTER_API_KEY || '').trim(); - const anthropicKey = (process.env.ANTHROPIC_API_KEY || '').trim(); - const openaiKey = (process.env.OPENAI_API_KEY || '').trim(); - const codexAccess = (process.env.OPENAI_CODEX_ACCESS_TOKEN || '').trim(); - const codexRefresh = (process.env.OPENAI_CODEX_REFRESH_TOKEN || '').trim(); - // OpenClaw compares profile.expires against Date.now() (milliseconds). - // The secret stores expires_at as Unix SECONDS (10-digit) from the OAuth - // device flow. Convert to ms here; pass-through if already 13-digit ms. - const _toMs = (s) => { const n = parseInt(s || '0', 10); return n > 0 && n < 10_000_000_000 ? n * 1000 : n; }; - const codexExpires = _toMs(process.env.OPENAI_CODEX_EXPIRES_AT); - const codexAccess2 = (process.env.OPENAI_CODEX_ACCESS_TOKEN_2 || '').trim(); - const codexRefresh2 = (process.env.OPENAI_CODEX_REFRESH_TOKEN_2 || '').trim(); - const codexExpires2 = _toMs(process.env.OPENAI_CODEX_EXPIRES_AT_2); - const codexIdToken2 = (process.env.OPENAI_CODEX_ID_TOKEN_2 || '').trim(); - const codexAccountId2 = (process.env.OPENAI_CODEX_ACCOUNT_ID_2 || '').trim(); - const codexAccess3 = (process.env.OPENAI_CODEX_ACCESS_TOKEN_3 || '').trim(); - const codexRefresh3 = (process.env.OPENAI_CODEX_REFRESH_TOKEN_3 || '').trim(); - const codexExpires3 = _toMs(process.env.OPENAI_CODEX_EXPIRES_AT_3); - const codexIdToken3 = (process.env.OPENAI_CODEX_ID_TOKEN_3 || '').trim(); - const codexAccountId3 = (process.env.OPENAI_CODEX_ACCOUNT_ID_3 || '').trim(); - if (!fs.existsSync(configPath)) { - console.log('[auth-seed] missing config:', configPath); - process.exit(0); - } - let cfg; - try { - cfg = JSON.parse(fs.readFileSync(configPath, 'utf8')); - } catch (err) { - console.log('[auth-seed] invalid config:', err.message); - process.exit(0); - } - // Preserve accounts from the existing PVC state across gateway restarts. - // The ConfigMap carries gateway settings but accounts are written to the PVC - // by the provisioner at runtime — the ConfigMap accounts section is often empty. - // Merging ensures agents stay connected after a pod restart without requiring - // a full reprovision-all. - let existingState = null; - if (fs.existsSync(stateConfigPath)) { - try { existingState = JSON.parse(fs.readFileSync(stateConfigPath, 'utf8')); } catch (e) {} - } - const existingAccounts = existingState?.channels?.commonly?.accounts || {}; - const cfgAccounts = cfg?.channels?.commonly?.accounts || {}; - // ConfigMap wins per-accountId (for updates), PVC-only accounts are preserved - const mergedAccounts = { ...existingAccounts, ...cfgAccounts }; - const preserved = Object.keys(existingAccounts).filter(k => !cfgAccounts[k]).length; - if (preserved > 0) { - cfg.channels = cfg.channels || {}; - cfg.channels.commonly = cfg.channels.commonly || {}; - cfg.channels.commonly.accounts = mergedAccounts; - console.log('[auth-seed] preserved', preserved, 'accounts from existing PVC state'); - } - // Preserve agents.list and bindings from PVC — these are written by the provisioner - // at runtime and are NOT in the ConfigMap. Without this, every gateway restart wipes - // all agent configs because cfg (ConfigMap) has an empty agents.list. - const existingAgents = existingState?.agents?.list; - if (Array.isArray(existingAgents) && existingAgents.length > 0) { - cfg.agents = cfg.agents || {}; - cfg.agents.list = existingAgents; - console.log('[auth-seed] preserved', existingAgents.length, 'agents from existing PVC state'); - } - const existingBindings = existingState?.bindings; - if (Array.isArray(existingBindings) && existingBindings.length > 0) { - cfg.bindings = existingBindings; - console.log('[auth-seed] preserved', existingBindings.length, 'bindings from existing PVC state'); - } - // Write merged config to PVC - fs.writeFileSync(stateConfigPath, JSON.stringify(cfg, null, 2)); - console.log('[auth-seed] wrote config to writable path:', stateConfigPath); - const accounts = cfg?.channels?.commonly?.accounts || {}; - const accountIds = Object.keys(accounts); - if (!accountIds.length) { - console.log('[auth-seed] no accounts found'); - process.exit(0); - } - accountIds.forEach((accountId) => { - const agentDir = path.join(stateDir, 'agents', accountId, 'agent'); - const authPath = path.join(agentDir, 'auth-profiles.json'); - const customProfiles = accounts[accountId]?.authProfiles; - const hasCustomProfiles = customProfiles && typeof customProfiles === 'object' - && Object.keys(customProfiles).length > 0; - fs.mkdirSync(agentDir, { recursive: true }); - // Load existing file if present (patch mode) to avoid clobbering live-refreshed tokens - let store; - if (!hasCustomProfiles && fs.existsSync(authPath)) { - try { store = JSON.parse(fs.readFileSync(authPath, 'utf8')); } catch (e) { store = null; } - store = store || { version: 1, profiles: {} }; - store.profiles = store.profiles || {}; - } else { - store = { version: 1, profiles: hasCustomProfiles ? { ...customProfiles } : {} }; - if (geminiKey && !store.profiles['google:default']) { - store.profiles['google:default'] = { type: 'api_key', provider: 'google', key: geminiKey }; - } - if (geminiKey2 && !store.profiles['google:backup']) { - store.profiles['google:backup'] = { type: 'api_key', provider: 'google', key: geminiKey2 }; - } - if (openrouterKey && !store.profiles['openrouter:default']) { - store.profiles['openrouter:default'] = { type: 'api_key', provider: 'openrouter', key: openrouterKey }; - } - if (anthropicKey && !store.profiles['anthropic:default']) { - store.profiles['anthropic:default'] = { type: 'api_key', provider: 'anthropic', key: anthropicKey }; - } - if (openaiKey && !store.profiles['openai:default']) { - store.profiles['openai:default'] = { type: 'api_key', provider: 'openai', key: openaiKey }; - } - } - // Always upsert codex OAuth if K8s secret has a token newer than what is on disk. - // Account 1 is the primary; account 2 is the fallback for rate-limit resilience. - const codexOrder = []; - // hasLiteLLMKey: community agents have sk- key via provisioner; account-2/3 have raw JWTs - // that LiteLLM rejects. When sk- is present, skip adding account-2/3 to codexOrder. - const _codexCliProfile = store.profiles['openai-codex:codex-cli']; - const hasLiteLLMKey = _codexCliProfile && typeof _codexCliProfile.access === 'string' && _codexCliProfile.access.startsWith('sk-'); - if (codexAccess && codexRefresh) { - const existing = store.profiles['openai-codex:codex-cli']; - const hasLiteLLMKey = existing && typeof existing.access === 'string' && existing.access.startsWith('sk-'); - if (!hasLiteLLMKey && (!existing || codexExpires > (existing.expires || 0))) { - store.profiles['openai-codex:codex-cli'] = { - type: 'oauth', provider: 'openai-codex', - access: codexAccess, refresh: codexRefresh, - expires: codexExpires || (Date.now() + 3600000), - }; - console.log('[auth-seed] upserted codex oauth account-1:', authPath); - } else if (hasLiteLLMKey) { - console.log('[auth-seed] skipped codex oauth upsert (LiteLLM key in place):', authPath); - } - // Push codex-cli (account-1) LAST below, after acct-2 + acct-3, so - // rotation tries the fresh tokens first. Track with a flag. - var _hasCodexCli = true; - } - // Account 2 fallback — skip entirely when LiteLLM key is in place so - // raw OAuth JWTs are never written to this profile. - if (codexAccess2 && codexRefresh2 && !hasLiteLLMKey) { - const existing2 = store.profiles['openai-codex:account-2']; - if (!existing2 || codexExpires2 > (existing2.expires || 0)) { - store.profiles['openai-codex:account-2'] = { - type: 'oauth', provider: 'openai-codex', - access: codexAccess2, refresh: codexRefresh2, - expires: codexExpires2 || (Date.now() + 3600000), - }; - console.log('[auth-seed] upserted codex oauth account-2:', authPath); - } - codexOrder.push('openai-codex:account-2'); - } - // order is written after all account blocks below so account-3 is included - // Also write ~/.codex/auth.json in chatgpt format so acpx uses chatgpt auth - // (not codex-api-key which requires api.responses.write scope). - // acpx reads a single auth.json — use account-1 as primary. - if (codexAccess && codexRefresh) { - const codexAuthDir = path.join(stateDir, 'agents', accountId, '.codex'); - const codexAuthPath = path.join(codexAuthDir, 'auth.json'); - const codexIdToken = (process.env.OPENAI_CODEX_ID_TOKEN || '').trim(); - const codexAccountId = (process.env.OPENAI_CODEX_ACCOUNT_ID || '').trim(); - const existingCodexAuth = fs.existsSync(codexAuthPath) - ? (() => { try { return JSON.parse(fs.readFileSync(codexAuthPath, 'utf8')); } catch { return null; } })() - : null; - const existingExpires = existingCodexAuth?.tokens?.expires_at - ? new Date(existingCodexAuth.tokens.expires_at).getTime() : 0; - if (!existingCodexAuth || codexExpires > existingExpires) { - fs.mkdirSync(codexAuthDir, { recursive: true }); - const codexAuth = { - auth_mode: 'chatgpt', - OPENAI_API_KEY: null, - tokens: { - ...(codexIdToken ? { id_token: codexIdToken } : {}), - access_token: codexAccess, - refresh_token: codexRefresh, - ...(codexAccountId ? { account_id: codexAccountId } : {}), - }, - last_refresh: new Date().toISOString(), - }; - fs.writeFileSync(codexAuthPath, JSON.stringify(codexAuth, null, 2)); - console.log('[auth-seed] wrote codex auth.json:', codexAuthPath); - // Write to shared PVC path so lifecycle postStart can copy it to HOME in the main container. - // (Init container and main container have separate ephemeral filesystems; only volumes are shared.) - const sharedCodexPath = path.join(stateDir, '.codex', 'auth.json'); - fs.mkdirSync(path.dirname(sharedCodexPath), { recursive: true }); - fs.writeFileSync(sharedCodexPath, JSON.stringify(codexAuth, null, 2)); - console.log('[auth-seed] wrote codex auth.json to shared PVC:', sharedCodexPath); - } - } - // Upsert account-2 fallback profile if K8s secret has a second Codex account - if (codexAccess2 && codexRefresh2) { - const existing2 = store.profiles['openai-codex:account-2']; - if (!existing2 || codexExpires2 > (existing2.expires || 0)) { - store.profiles['openai-codex:account-2'] = { - type: 'oauth', provider: 'openai-codex', - access: codexAccess2, refresh: codexRefresh2, - expires: codexExpires2 || (Date.now() + 3600000), - }; - console.log('[auth-seed] upserted codex oauth account-2:', authPath); - } - // Write auth-2.json to shared PVC so acpx_run can swap to account-2 on rate-limit. - const auth2Json = { - auth_mode: 'chatgpt', - OPENAI_API_KEY: null, - tokens: { - ...(codexIdToken2 ? { id_token: codexIdToken2 } : {}), - access_token: codexAccess2, - refresh_token: codexRefresh2, - ...(codexAccountId2 ? { account_id: codexAccountId2 } : {}), - }, - last_refresh: new Date().toISOString(), - }; - const sharedAuth2Path = path.join(stateDir, '.codex', 'auth-2.json'); - fs.mkdirSync(path.dirname(sharedAuth2Path), { recursive: true }); - fs.writeFileSync(sharedAuth2Path, JSON.stringify(auth2Json, null, 2)); - console.log('[auth-seed] wrote codex auth-2.json to shared PVC:', sharedAuth2Path); - } - // Upsert account-3 fallback profile if K8s secret has a third Codex account. - // Skip when LiteLLM key is in place so raw OAuth JWTs are never written. - // Account rotation is handled by LiteLLM internally — no direct OAuth fallback needed. - if (codexAccess3 && codexRefresh3 && !hasLiteLLMKey) { - const existing3 = store.profiles['openai-codex:account-3']; - if (!existing3 || codexExpires3 > (existing3.expires || 0)) { - store.profiles['openai-codex:account-3'] = { - type: 'oauth', provider: 'openai-codex', - access: codexAccess3, refresh: codexRefresh3, - expires: codexExpires3 || (Date.now() + 3600000), - }; - console.log('[auth-seed] upserted codex oauth account-3:', authPath); - } - const auth3Json = { - auth_mode: 'chatgpt', - OPENAI_API_KEY: null, - tokens: { - ...(codexIdToken3 ? { id_token: codexIdToken3 } : {}), - access_token: codexAccess3, - refresh_token: codexRefresh3, - ...(codexAccountId3 ? { account_id: codexAccountId3 } : {}), - }, - last_refresh: new Date().toISOString(), - }; - const sharedAuth3Path = path.join(stateDir, '.codex', 'auth-3.json'); - fs.mkdirSync(path.dirname(sharedAuth3Path), { recursive: true }); - fs.writeFileSync(sharedAuth3Path, JSON.stringify(auth3Json, null, 2)); - console.log('[auth-seed] wrote codex auth-3.json to shared PVC:', sharedAuth3Path); - codexOrder.push('openai-codex:account-3'); - } - // Append account-1 at the END so rotation tries fresh accts first. - // Remove this when account-1 is re-logged-in and fresh. - if (typeof _hasCodexCli !== 'undefined' && _hasCodexCli) { - codexOrder.push('openai-codex:codex-cli'); - } - // Persist auth order so OpenClaw falls back across accounts on rate-limit - // NOTE: field must be 'order' (not 'authOrder') — OpenClaw reads store.order - if (codexOrder.length > 0) { - store.order = store.order || {}; - store.order['openai-codex'] = codexOrder; - } - fs.writeFileSync(authPath, JSON.stringify(store, null, 2)); - console.log('[auth-seed] wrote:', authPath, hasCustomProfiles ? 'custom profiles' : 'patched'); - }); - // Also write auth-profiles.json to the gateway 'main' agentDir — OpenClaw resolves - // global LLM model auth from /state/agents/main/agent/auth-profiles.json, not the - // per-account agentDir. Use the last account's store as representative (all accounts - // share the same codex credentials). - const mainAgentDir = path.join(stateDir, 'agents', 'main', 'agent'); - const mainAuthPath = path.join(mainAgentDir, 'auth-profiles.json'); - if (accountIds.length > 0) { - const repAuthPath = path.join(stateDir, 'agents', accountIds[0], 'agent', 'auth-profiles.json'); - if (fs.existsSync(repAuthPath)) { - fs.mkdirSync(mainAgentDir, { recursive: true }); - fs.copyFileSync(repAuthPath, mainAuthPath); - console.log('[auth-seed] copied auth-profiles.json to main agentDir:', mainAuthPath); - } - } - // Write global git identity to PVC so main container picks it up via GIT_CONFIG_GLOBAL. - const gitUserName = (process.env.GIT_USER_NAME || '').trim(); - const gitUserEmail = (process.env.GIT_USER_EMAIL || '').trim(); - if (gitUserName && gitUserEmail) { - const gitConfigPath = path.join(stateDir, '.gitconfig'); - const gitConfig = `[user]\n\tname = ${gitUserName}\n\temail = ${gitUserEmail}\n`; - fs.writeFileSync(gitConfigPath, gitConfig); - console.log('[auth-seed] wrote git identity:', gitUserName, '<' + gitUserEmail + '>'); - } - NODE - env: - - name: OPENCLAW_CONFIG_PATH - value: /config/moltbot.json - - name: OPENCLAW_STATE_DIR - value: /state - - name: GEMINI_API_KEY - valueFrom: - secretKeyRef: - name: api-keys - key: gemini-api-key - - name: GEMINI_API_KEY_2 - valueFrom: - secretKeyRef: - name: api-keys - key: gemini-api-key-2 - optional: true - - name: ANTHROPIC_API_KEY - valueFrom: - secretKeyRef: - name: api-keys - key: anthropic-api-key - optional: true - - name: OPENROUTER_API_KEY - valueFrom: - secretKeyRef: - name: api-keys - key: openrouter-api-key - optional: true - - name: OPENAI_API_KEY - valueFrom: - secretKeyRef: - name: api-keys - key: openai-api-key - optional: true - - name: OPENAI_CODEX_ACCESS_TOKEN - valueFrom: - secretKeyRef: - name: api-keys - key: openai-codex-access-token - optional: true - - name: OPENAI_CODEX_REFRESH_TOKEN - valueFrom: - secretKeyRef: - name: api-keys - key: openai-codex-refresh-token - optional: true - - name: OPENAI_CODEX_EXPIRES_AT - valueFrom: - secretKeyRef: - name: api-keys - key: openai-codex-expires-at - optional: true - - name: OPENAI_CODEX_ID_TOKEN - valueFrom: - secretKeyRef: - name: api-keys - key: openai-codex-id-token - optional: true - - name: OPENAI_CODEX_ACCOUNT_ID - valueFrom: - secretKeyRef: - name: api-keys - key: openai-codex-account-id - optional: true - - name: OPENAI_CODEX_ACCESS_TOKEN_2 - valueFrom: - secretKeyRef: - name: api-keys - key: openai-codex-access-token-2 - optional: true - - name: OPENAI_CODEX_REFRESH_TOKEN_2 - valueFrom: - secretKeyRef: - name: api-keys - key: openai-codex-refresh-token-2 - optional: true - - name: OPENAI_CODEX_EXPIRES_AT_2 - valueFrom: - secretKeyRef: - name: api-keys - key: openai-codex-expires-at-2 - optional: true - - name: OPENAI_CODEX_ID_TOKEN_2 - valueFrom: - secretKeyRef: - name: api-keys - key: openai-codex-id-token-2 - optional: true - - name: OPENAI_CODEX_ACCOUNT_ID_2 - valueFrom: - secretKeyRef: - name: api-keys - key: openai-codex-account-id-2 - optional: true - - name: OPENAI_CODEX_ACCESS_TOKEN_3 - valueFrom: - secretKeyRef: - name: api-keys - key: openai-codex-access-token-3 - optional: true - - name: OPENAI_CODEX_REFRESH_TOKEN_3 - valueFrom: - secretKeyRef: - name: api-keys - key: openai-codex-refresh-token-3 - optional: true - - name: OPENAI_CODEX_EXPIRES_AT_3 - valueFrom: - secretKeyRef: - name: api-keys - key: openai-codex-expires-at-3 - optional: true - - name: OPENAI_CODEX_ID_TOKEN_3 - valueFrom: - secretKeyRef: - name: api-keys - key: openai-codex-id-token-3 - optional: true - - name: OPENAI_CODEX_ACCOUNT_ID_3 - valueFrom: - secretKeyRef: - name: api-keys - key: openai-codex-account-id-3 - optional: true - - name: GIT_USER_NAME - valueFrom: - secretKeyRef: - name: api-keys - key: git-user-name - optional: true - - name: GIT_USER_EMAIL - valueFrom: - secretKeyRef: - name: api-keys - key: git-user-email - optional: true - volumeMounts: - - name: clawdbot-config - mountPath: /config - readOnly: true - - name: clawdbot-state - mountPath: /state - # ADR-005 Stage 2: install codex CLI + @commonly/cli into a shared - # volume the gateway container mounts. Avoids modifying the - # _external/clawdbot Dockerfile (a submodule on the openclaw fork) — - # the install lives in helm so it ships with the deploy. - # - # `node:22-bookworm-slim` matches the runtime container's Node major - # so any libc-linked binaries are compatible. `git` isn't in slim by - # default; we apt-install it because @commonly/cli isn't published - # to npm yet (ADR-005 Phase 4) and must be installed from source. - # - # Soft-fail: if the npm registry or github is unreachable at pod- - # restart time, the gateway should still start — the run loop is an - # operator-started manual step (Stage 2), and `acpx_run` continues - # to work as a fallback. A hard-failing init container would strand - # the entire gateway pod for an issue with one optional capability. - - name: codex-tools-installer - image: node:22-bookworm-slim - command: - - /bin/sh - - -c - - | - install_codex_tools() { - set -e - export NPM_CONFIG_PREFIX=/tools - apt-get update >/dev/null 2>&1 - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - git ca-certificates >/dev/null 2>&1 - # Pin codex by version (npm published) — see values.yaml. - npm install --global --no-audit --no-fund \ - "@openai/codex@{{ .Values.agents.clawdbot.codexTools.codexVersion | default "0.125.0" }}" - # @commonly/cli — install from source at the pinned ref. The - # cli/ subdirectory is a self-contained npm package with one - # runtime dep (commander); ~200KB of source. - git clone --depth 1 --branch "{{ .Values.agents.clawdbot.codexTools.commonlyCliRef | default "main" }}" \ - https://github.com/Team-Commonly/commonly.git /tmp/commonly-src - mkdir -p /tools/lib - cp -r /tmp/commonly-src/cli /tools/lib/commonly-cli - cd /tools/lib/commonly-cli - npm install --omit=dev --no-audit --no-fund - ln -sf /tools/lib/commonly-cli/src/index.js /tools/bin/commonly - chmod +x /tools/lib/commonly-cli/src/index.js - # Verify (best-effort; failures here don't block since the - # outer || already swallowed earlier failures we care about). - /tools/bin/codex --version || true - /tools/bin/commonly --version || true - ls -la /tools/bin/ || true - } - install_codex_tools || { - echo "[codex-tools-installer] install failed — gateway will start without /tools binaries" - echo "[codex-tools-installer] operator: 'commonly agent run codex' is unavailable until next pod restart" - exit 0 - } - volumeMounts: - - name: codex-tools - mountPath: /tools - containers: - - name: clawdbot-gateway - image: "{{ .Values.agents.clawdbot.image.repository }}:{{ .Values.agents.clawdbot.image.tag }}" - imagePullPolicy: {{ .Values.agents.clawdbot.image.pullPolicy }} - command: - - node - - dist/index.js - - gateway - - --bind - - {{ .Values.agents.clawdbot.config.gatewayBind | default "lan" | quote }} - - --port - - {{ .Values.agents.clawdbot.config.gatewayPort | default 18789 | quote }} - - --allow-unconfigured - lifecycle: - postStart: - exec: - command: - - /bin/sh - - -c - # Two unrelated bootstrap steps bundled into one postStart hook - # because k8s only allows ONE exec command per lifecycle phase: - # - # 1. Copy codex auth.json into node user's HOME so the CLI - # can find it on `kubectl exec` invocations. - # - # 2. Wire GITHUB_PAT into git credential.helper so subagents - # (acpx_run sub-codex invocations, ad-hoc operator - # `kubectl exec ... git clone`) can authenticate against - # private repos. PR #382 plumbed the env var but never - # wired the credential helper — surfaced in smoke - # 2026-05-20 when Theo tried `git clone` and got - # `could not read Username for 'https://github.com'`. - # The cloud-codex boot script does the same wiring; this - # is the moltbot-side equivalent so both runtimes are - # consistent. Idempotent — repeated postStart invocations - # (on container restart) rewrite the same file. - # Silent no-op when GITHUB_PAT is empty to keep the hook - # safe across community-tier deployments that ship no - # PAT by design. - - | - set +e - mkdir -p /home/node/.codex - cp /state/.codex/auth.json /home/node/.codex/auth.json 2>/dev/null || true - if [ -n "${GITHUB_PAT:-}" ]; then - # Trim trailing newline / CR from the PAT — base64-decoded - # secret values often carry a trailing newline that git's - # libcurl refuses to parse ("credential url cannot be - # parsed"), verified empirically on cloud-codex 2026-05-15. - TRIMMED_PAT=$(printf '%s' "${GITHUB_PAT}" | tr -d '\n\r') - printf 'https://x-access-token:%s@github.com\n' "${TRIMMED_PAT}" > /state/.git-credentials - chmod 600 /state/.git-credentials - git config --global credential.helper "store --file=/state/.git-credentials" - # GIT_AUTHOR_NAME / GIT_AUTHOR_EMAIL are read from the - # container env if present, otherwise default to the - # generic Clawdbot identity. They're NOT declared in the - # `env:` block below by design — there's currently no per- - # agent author override flowing through Helm values. If - # operators ever want to override (e.g. per-instanceId - # author for clearer commit attribution), add them to - # the env: block as plain `value:` entries OR wire them - # through `values.yaml` -> `.Values.agents.clawdbot.git.authorName`. - # The fallbacks below keep the generic identity as the - # safe default — git commits + PR opens attribute to - # "Clawdbot Agent " without - # additional configuration. - git config --global user.name "${GIT_AUTHOR_NAME:-Clawdbot Agent}" - git config --global user.email "${GIT_AUTHOR_EMAIL:-clawdbot@commonly.me}" - # Seed gh CLI auth so `gh pr create` works non-interactively. - # gh reads $GH_CONFIG_DIR/hosts.yml (env points it at /state/gh, - # on the PVC so acpx sub-agents share it). Reuses TRIMMED_PAT so - # a trailing newline in the secret can't break the token. - mkdir -p /state/gh - printf 'github.com:\n oauth_token: %s\n user: x-access-token\n git_protocol: https\n' "${TRIMMED_PAT}" > /state/gh/hosts.yml - chmod 600 /state/gh/hosts.yml - echo "[clawdbot postStart] git + gh credentials seeded from GITHUB_PAT" - else - echo "[clawdbot postStart] GITHUB_PAT empty — git/gh operations will require interactive auth" - fi - exit 0 - env: - # ADR-005 Stage 2: prepend the codex-tools volume to PATH so - # `codex` and `commonly` are on PATH for operator `kubectl exec` - # invocations and the run-loop process. Order matters — these - # binaries shadow any same-named ones in the openclaw image. - - name: PATH - value: "/tools/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - - name: CLAWDBOT_GATEWAY_PORT - value: {{ .Values.agents.clawdbot.config.gatewayPort | default 18789 | quote }} - - name: CLAWDBOT_GATEWAY_BIND - value: {{ .Values.agents.clawdbot.config.gatewayBind | default "lan" | quote }} - - name: CLAWDBOT_GATEWAY_TOKEN - valueFrom: - secretKeyRef: - name: api-keys - key: clawdbot-gateway-token - - name: OPENCLAW_GATEWAY_TOKEN - valueFrom: - secretKeyRef: - name: api-keys - key: clawdbot-gateway-token - - name: CLAWDBOT_CONFIG_DIR - value: /config - - name: OPENCLAW_STATE_DIR - value: /state - - name: OPENCLAW_CONFIG_PATH - value: /state/moltbot.json - - name: CLAWDBOT_WORKSPACE_DIR - value: /workspace - - name: CLAWDBOT_SKIP_BROWSER_CONTROL_SERVER - value: "1" - - name: CLAWDBOT_SKIP_CANVAS_HOST - value: "1" - - name: COMMONLY_API_URL - value: {{ .Values.backend.env.commonlyApiUrl | default (printf "http://backend.%s.svc.cluster.local:5000" (include "commonly.namespace" .)) | quote }} - - name: GEMINI_API_KEY - valueFrom: - secretKeyRef: - name: api-keys - key: gemini-api-key - - name: ANTHROPIC_API_KEY - valueFrom: - secretKeyRef: - name: api-keys - key: anthropic-api-key - optional: true - - name: OPENAI_API_KEY - valueFrom: - secretKeyRef: - name: api-keys - key: openai-api-key - optional: true - - name: OPENROUTER_API_KEY - valueFrom: - secretKeyRef: - name: api-keys - key: openrouter-api-key - optional: true - - name: BRAVE_API_KEY - valueFrom: - secretKeyRef: - name: api-keys - key: brave-api-key - optional: true - - name: BRAVE_API_KEY_2 - valueFrom: - secretKeyRef: - name: api-keys - key: brave-api-key-2 - optional: true - - name: FIRECRAWL_API_KEY - valueFrom: - secretKeyRef: - name: api-keys - key: firecrawl-api-key - optional: true - - name: DEEPGRAM_API_KEY - valueFrom: - secretKeyRef: - name: api-keys - key: deepgram-api-key - optional: true - # GitHub PAT — inherited by acpx_run sub-agents so they can clone repos and open PRs - - name: GITHUB_PAT - valueFrom: - secretKeyRef: - name: api-keys - key: GITHUB_PAT - optional: true - # gh CLI config dir on the /state PVC. The postStart hook writes - # $GH_CONFIG_DIR/hosts.yml from GITHUB_PAT so `gh pr create` works - # non-interactively; setting it as env (not relying on $HOME) means - # acpx_run sub-agents inherit the same auth location. - - name: GH_CONFIG_DIR - value: /state/gh - # Git identity — written to PVC by init container; picked up here via GIT_CONFIG_GLOBAL - - name: GIT_CONFIG_GLOBAL - value: /state/.gitconfig - # LiteLLM — routes acpx_run calls through LiteLLM for unified logging + model fallback - - name: LITELLM_BASE_URL - value: "http://litellm:4000" - - name: LITELLM_MASTER_KEY - valueFrom: - secretKeyRef: - name: api-keys - key: litellm-master-key - optional: true - ports: - - containerPort: {{ .Values.agents.clawdbot.config.gatewayPort | default 18789 }} - name: gateway - - containerPort: {{ .Values.agents.clawdbot.config.bridgePort | default 18790 }} - name: bridge - resources: - {{- toYaml .Values.agents.clawdbot.resources | nindent 10 }} - volumeMounts: - - name: clawdbot-config - mountPath: /config - readOnly: true - - name: clawdbot-state - mountPath: /state - - name: clawdbot-workspace - mountPath: /workspace - # ADR-005 Stage 2 — codex + commonly CLIs installed by the - # codex-tools-installer init container. Mounted read-only here - # since nothing in the main container needs to write to it. - - name: codex-tools - mountPath: /tools - readOnly: true - volumes: - - name: clawdbot-config - configMap: - name: clawdbot-config - - name: clawdbot-state - persistentVolumeClaim: - claimName: clawdbot-config-pvc - - name: clawdbot-workspace - persistentVolumeClaim: - claimName: clawdbot-workspace-pvc - # emptyDir is fine — re-populated on every pod start by the init - # container. Adds ~30s to startup but avoids a PVC + invalidation - # logic. Revisit if pod-restart latency becomes a real concern. - - name: codex-tools - emptyDir: {} - {{- with .Values.agents.clawdbot.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.agents.clawdbot.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} -{{- end }} diff --git a/k8s/helm/commonly/templates/agents/clawdbot-service.yaml b/k8s/helm/commonly/templates/agents/clawdbot-service.yaml deleted file mode 100644 index 3719ee6104..0000000000 --- a/k8s/helm/commonly/templates/agents/clawdbot-service.yaml +++ /dev/null @@ -1,22 +0,0 @@ -{{- if .Values.agents.clawdbot.enabled }} -apiVersion: v1 -kind: Service -metadata: - name: clawdbot-gateway - namespace: {{ include "commonly.namespace" . }} - labels: - {{- include "commonly.labels" . | nindent 4 }} - app: clawdbot-gateway -spec: - type: ClusterIP - selector: - {{- include "commonly.selectorLabels" . | nindent 4 }} - app: clawdbot-gateway - ports: - - name: gateway - port: {{ .Values.agents.clawdbot.config.gatewayPort | default 18789 }} - targetPort: gateway - - name: bridge - port: {{ .Values.agents.clawdbot.config.bridgePort | default 18790 }} - targetPort: bridge -{{- end }} diff --git a/k8s/helm/commonly/templates/agents/cloud-codex-deployment.yaml b/k8s/helm/commonly/templates/agents/cloud-codex-deployment.yaml index af378a60b2..4fe119a2cf 100644 --- a/k8s/helm/commonly/templates/agents/cloud-codex-deployment.yaml +++ b/k8s/helm/commonly/templates/agents/cloud-codex-deployment.yaml @@ -81,7 +81,7 @@ spec: agent: {{ $name }} spec: initContainers: - # Mirror of clawdbot-deployment's codex-tools-installer init — + # Codex tools installer init — # installs the pinned codex + commonly CLIs into the shared # /tools volume so the main container has the binaries available. - name: codex-tools-installer diff --git a/k8s/helm/commonly/templates/configmaps/agent-configs.yaml b/k8s/helm/commonly/templates/configmaps/agent-configs.yaml index 9799a781dc..40e8f4cd06 100644 --- a/k8s/helm/commonly/templates/configmaps/agent-configs.yaml +++ b/k8s/helm/commonly/templates/configmaps/agent-configs.yaml @@ -1,118 +1,3 @@ ---- -# ConfigMap for Clawdbot (moltbot) configuration -apiVersion: v1 -kind: ConfigMap -metadata: - name: clawdbot-config - namespace: {{ include "commonly.namespace" . }} - labels: - {{- include "commonly.labels" . | nindent 4 }} - app: clawdbot - component: config -data: - moltbot.json: | - { - "models": { - "providers": { - {{- if .Values.litellm.codexBypassLitellm }} - "openai-codex": { - "baseUrl": "https://chatgpt.com/backend-api", - "api": "openai-codex-responses", - "models": [ - {"id": "gpt-5.4", "name": "gpt-5.4"}, - {"id": "gpt-5.4-mini", "name": "gpt-5.4-mini"}, - {"id": "gpt-5.3-codex", "name": "gpt-5.3-codex"} - ] - } - {{- else if .Values.litellm.enabled }} - "openai-codex": { - "baseUrl": "http://litellm.{{ include "commonly.namespace" . }}.svc.cluster.local:4000", - "api": "openai-completions", - "models": [ - {"id": "gpt-5.4", "name": "gpt-5.4"}, - {"id": "gpt-5.3-codex", "name": "gpt-5.3-codex"} - ] - } - {{- else }} - "openai-codex": null - {{- end }} - } - }, - "agents": { - "defaults": { - "model": { - "primary": "openrouter/nvidia/nemotron-3-super-120b-a12b:free", - "fallbacks": ["google/gemini-2.5-flash", "google/gemini-2.5-flash-lite", "google/gemini-2.0-flash"] - }, - "maxConcurrent": 4, - "subagents": { - "maxConcurrent": 8 - } - }, - "list": [] - }, - "commands": { - "native": "auto", - "nativeSkills": "auto" - }, - "channels": { - "commonly": { - "enabled": true, - "baseUrl": "http://backend.{{ include "commonly.namespace" . }}.svc.cluster.local:5000", - "accounts": {} - } - }, - "gateway": { - "mode": "local", - "bind": "lan", - "auth": { - "mode": "token" - }, - "controlUi": { - "allowInsecureAuth": true - }, - "http": { - "endpoints": { - "chatCompletions": { - "enabled": true - } - } - } - }, - "messages": { - "ackReactionScope": "group-mentions" - }, - "bindings": [], - "session": { - "scope": "per-sender", - "dmScope": "per-channel-peer", - "maintenance": { - "pruneAfter": "30d", - "maxEntries": 1000 - } - }, - "tools": { - "sessions": { - "visibility": "all" - } - }, - "plugins": { - "entries": { - "commonly": { - "enabled": true - }, - "acpx": { - "enabled": true, - "config": { - "permissionMode": "approve-all", - "nonInteractivePermissions": "fail", - "cwd": "/workspace" - } - } - } - } - } - --- # ConfigMap for Commonly Bot (internal) configuration {{- $existingCommonlyBotConfig := (lookup "v1" "ConfigMap" (include "commonly.namespace" .) "commonly-bot-config") }} diff --git a/k8s/helm/commonly/templates/core/backend-deployment.yaml b/k8s/helm/commonly/templates/core/backend-deployment.yaml index 54aa6bbb6a..9258e90ad7 100644 --- a/k8s/helm/commonly/templates/core/backend-deployment.yaml +++ b/k8s/helm/commonly/templates/core/backend-deployment.yaml @@ -405,17 +405,6 @@ spec: value: {{ include "commonly.namespace" . | quote }} - name: COMMONLY_API_URL value: {{ .Values.backend.env.commonlyApiUrl | default (printf "http://backend.%s.svc.cluster.local:5000" (include "commonly.namespace" .)) | quote }} - - name: CLAWDBOT_GATEWAY_URL - value: {{ .Values.backend.env.clawdbotGatewayUrl | default (printf "http://clawdbot-gateway.%s.svc.cluster.local:18789" (include "commonly.namespace" .)) | quote }} - - name: MOLTBOT_GATEWAY_URL - value: {{ .Values.backend.env.moltbotGatewayUrl | default (printf "ws://clawdbot-gateway.%s.svc.cluster.local:18789" (include "commonly.namespace" .)) | quote }} - - name: CLAWDBOT_IMAGE - value: "{{ .Values.agents.clawdbot.image.repository }}:{{ .Values.agents.clawdbot.image.tag }}" - - name: CLAWDBOT_GATEWAY_TOKEN - valueFrom: - secretKeyRef: - name: api-keys - key: clawdbot-gateway-token - name: COMMONLY_BOT_RUNTIME_TOKEN valueFrom: secretKeyRef: diff --git a/k8s/helm/commonly/templates/ops/ops-runner-deployment.yaml b/k8s/helm/commonly/templates/ops/ops-runner-deployment.yaml index 98b89cb749..1abd745aa6 100644 --- a/k8s/helm/commonly/templates/ops/ops-runner-deployment.yaml +++ b/k8s/helm/commonly/templates/ops/ops-runner-deployment.yaml @@ -385,17 +385,6 @@ spec: value: {{ include "commonly.namespace" . | quote }} - name: COMMONLY_API_URL value: {{ .Values.backend.env.commonlyApiUrl | default (printf "http://backend.%s.svc.cluster.local:5000" (include "commonly.namespace" .)) | quote }} - - name: CLAWDBOT_GATEWAY_URL - value: {{ .Values.backend.env.clawdbotGatewayUrl | default (printf "http://clawdbot-gateway.%s.svc.cluster.local:18789" (include "commonly.namespace" .)) | quote }} - - name: MOLTBOT_GATEWAY_URL - value: {{ .Values.backend.env.moltbotGatewayUrl | default (printf "ws://clawdbot-gateway.%s.svc.cluster.local:18789" (include "commonly.namespace" .)) | quote }} - - name: CLAWDBOT_IMAGE - value: "{{ .Values.agents.clawdbot.image.repository }}:{{ .Values.agents.clawdbot.image.tag }}" - - name: CLAWDBOT_GATEWAY_TOKEN - valueFrom: - secretKeyRef: - name: api-keys - key: clawdbot-gateway-token - name: COMMONLY_BOT_RUNTIME_TOKEN valueFrom: secretKeyRef: diff --git a/k8s/helm/commonly/templates/pvcs/clawdbot-config-pvc.yaml b/k8s/helm/commonly/templates/pvcs/clawdbot-config-pvc.yaml deleted file mode 100644 index 008faf0c8c..0000000000 --- a/k8s/helm/commonly/templates/pvcs/clawdbot-config-pvc.yaml +++ /dev/null @@ -1,18 +0,0 @@ -{{- if .Values.agents.clawdbot.persistence.config.enabled }} -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: clawdbot-config-pvc - namespace: {{ include "commonly.namespace" . }} - labels: - {{- include "commonly.labels" . | nindent 4 }} - app: clawdbot - component: config -spec: - accessModes: - - {{ .Values.agents.clawdbot.persistence.config.accessMode }} - storageClassName: {{ .Values.agents.clawdbot.persistence.config.storageClass }} - resources: - requests: - storage: {{ .Values.agents.clawdbot.persistence.config.size }} -{{- end }} diff --git a/k8s/helm/commonly/templates/pvcs/clawdbot-workspace-pvc.yaml b/k8s/helm/commonly/templates/pvcs/clawdbot-workspace-pvc.yaml deleted file mode 100644 index d78f4d2c44..0000000000 --- a/k8s/helm/commonly/templates/pvcs/clawdbot-workspace-pvc.yaml +++ /dev/null @@ -1,18 +0,0 @@ -{{- if .Values.agents.clawdbot.persistence.workspace.enabled }} -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: clawdbot-workspace-pvc - namespace: {{ include "commonly.namespace" . }} - labels: - {{- include "commonly.labels" . | nindent 4 }} - app: clawdbot - component: workspace -spec: - accessModes: - - {{ .Values.agents.clawdbot.persistence.workspace.accessMode }} - storageClassName: {{ .Values.agents.clawdbot.persistence.workspace.storageClass }} - resources: - requests: - storage: {{ .Values.agents.clawdbot.persistence.workspace.size }} -{{- end }} diff --git a/k8s/helm/commonly/templates/pvcs/filestore-storageclass.yaml b/k8s/helm/commonly/templates/pvcs/filestore-storageclass.yaml deleted file mode 100644 index c9587e8458..0000000000 --- a/k8s/helm/commonly/templates/pvcs/filestore-storageclass.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if .Values.agents.clawdbot.persistence.enabled }} -apiVersion: storage.k8s.io/v1 -kind: StorageClass -metadata: - name: filestore-sc -provisioner: filestore.csi.storage.gke.io -parameters: - tier: standard - network: default -allowVolumeExpansion: true -volumeBindingMode: Immediate -{{- end }} diff --git a/k8s/helm/commonly/templates/secrets/api-keys.yaml b/k8s/helm/commonly/templates/secrets/api-keys.yaml index d21a277fe9..d9f1495d76 100644 --- a/k8s/helm/commonly/templates/secrets/api-keys.yaml +++ b/k8s/helm/commonly/templates/secrets/api-keys.yaml @@ -184,9 +184,6 @@ spec: key: {{ $keyPrefix }}openai-codex-client-id # Agent Runtime Tokens - - secretKey: clawdbot-gateway-token - remoteRef: - key: {{ $keyPrefix }}clawdbot-gateway-token - secretKey: commonly-bot-runtime-token remoteRef: key: {{ $keyPrefix }}commonly-bot-runtime-token @@ -244,12 +241,6 @@ spec: remoteRef: key: {{ $keyPrefix }}brave-api-key-2 - # GitHub — PAT for agent git operations (upgrade to GitHub App later) - {{- if .Values.githubPat.enabled }} - - secretKey: GITHUB_PAT - remoteRef: - key: commonly-github-pat - {{- end }} # GitHub App credentials for the room-grant broker (TASK-033). Create all three # remote keys in Secret Manager BEFORE merging: ESO rejects the whole api-keys diff --git a/k8s/helm/commonly/templates/secrets/local-secrets.yaml b/k8s/helm/commonly/templates/secrets/local-secrets.yaml index c45ce2bdde..e13fe2f8fd 100644 --- a/k8s/helm/commonly/templates/secrets/local-secrets.yaml +++ b/k8s/helm/commonly/templates/secrets/local-secrets.yaml @@ -36,10 +36,8 @@ stringData: github-client-secret: {{ .Values.localSecrets.githubClientSecret | default "" | quote }} x-oauth-client-id: {{ .Values.localSecrets.xOauthClientId | default "" | quote }} x-oauth-client-secret: {{ .Values.localSecrets.xOauthClientSecret | default "" | quote }} - GITHUB_PAT: {{ .Values.localSecrets.githubPat | default "" | quote }} # Agent runtime tokens — generated placeholder values for local use - clawdbot-gateway-token: {{ .Values.localSecrets.clawdbotGatewayToken | default "local-gateway-token" | quote }} commonly-bot-runtime-token: {{ .Values.localSecrets.commonlyBotRuntimeToken | default "local-bot-token" | quote }} # Git identity — used by init container to write /state/.gitconfig diff --git a/k8s/helm/commonly/values-dev.yaml b/k8s/helm/commonly/values-dev.yaml index 843ad1f0d6..512a4f4974 100644 --- a/k8s/helm/commonly/values-dev.yaml +++ b/k8s/helm/commonly/values-dev.yaml @@ -30,7 +30,6 @@ billing: agentProvisioning: enabled: true - createWorkspacePvc: false backend: replicaCount: 1 @@ -78,8 +77,6 @@ backend: commonlyApiUrl: "" # ADR-023 W2 hosted runtime (workers/agent-runtime, deployed 2026-08-30). hostedRuntimeUrl: "https://commonly-agent-runtime.commonlyai.workers.dev" - clawdbotGatewayUrl: "" - moltbotGatewayUrl: "" pgHost: "YOUR_PG_HOST" pgPort: "25450" pgDatabase: "defaultdb" @@ -209,43 +206,6 @@ externalSecrets: clusterName: commonly-dev agents: - clawdbot: - # PARKED 2026-08-20 (Sam) — the moltbot tier is retiring in favour of - # pi/OpenCode wrapper adapters (ADR-021/ADR-023, #1050). Its credential - # stack had been dead since 2026-07-02 — both cluster codex tokens expired, - # OpenRouter 401 "User not found" — and nobody noticed for seven weeks, - # because the native and BYO tiers never touch those credentials. - # - # This flag is the durable half of the park. The manual - # `kubectl scale --replicas=0` alone would be UNDONE by the next Deploy Dev, - # because the template hardcodes `replicas: 1` behind this flag and helm - # upgrade restores whatever the chart declares. Identity continuity holds - # either way: User rows, memory, and installations survive (ADR-001), so - # the same agents return as themselves on the replacement runtime. - enabled: false - image: - repository: gcr.io/YOUR_GCP_PROJECT_ID/clawdbot-gateway - tag: "20260414192025" - pullPolicy: Always - strategy: - type: Recreate - persistence: - config: - storageClass: standard-rwo - size: 1Gi - accessMode: ReadWriteOnce - workspace: - storageClass: standard-rwo - size: 10Gi - accessMode: ReadWriteOnce - nodeSelector: - pool: dev - tolerations: - - key: "pool" - operator: "Equal" - value: "dev" - effect: "NoSchedule" - commonlyBot: enabled: true image: diff --git a/k8s/helm/commonly/values-local.yaml b/k8s/helm/commonly/values-local.yaml index dd2e5fd4b1..33bb274a54 100644 --- a/k8s/helm/commonly/values-local.yaml +++ b/k8s/helm/commonly/values-local.yaml @@ -4,7 +4,7 @@ # - In-cluster MongoDB and PostgreSQL (no Aiven) # - No GCP Secret Manager / ExternalSecrets # - No Cloudflare tunnel -# - No agent runtime (clawdbot/litellm) — enable manually if needed +# - No hosted model proxy (litellm) — enable manually if needed # # Requires: kind, kubectl, helm # @@ -47,8 +47,6 @@ backend: frontendUrl: "http://localhost:3000" backendUrl: "http://localhost:5000" commonlyApiUrl: "" - clawdbotGatewayUrl: "" - moltbotGatewayUrl: "" # In-cluster Postgres (no Aiven, no SSL) pgHost: "" # empty → defaults to postgres..svc.cluster.local pgPort: "5432" @@ -184,7 +182,6 @@ localSecrets: googleClientSecret: "" githubClientId: "" githubClientSecret: "" - githubPat: "" xOauthClientId: "" xOauthClientSecret: "" slackClientId: "" @@ -198,19 +195,11 @@ localSecrets: braveApiKey2: "" firecrawlApiKey: "" deepgramApiKey: "" - clawdbotGatewayToken: "local-gateway-token" commonlyBotRuntimeToken: "local-bot-token" # ── Disabled cloud-only services ────────────────────────────────────────────── agents: - clawdbot: - enabled: false # enable manually: --set agents.clawdbot.enabled=true - persistence: - config: - enabled: false # no PVCs needed when clawdbot is disabled - workspace: - enabled: false commonlyBot: enabled: false @@ -255,7 +244,6 @@ rbac: agentProvisioning: enabled: true - createWorkspacePvc: false # ── Autoscaling / monitoring off ────────────────────────────────────────────── diff --git a/k8s/helm/commonly/values-staging.yaml b/k8s/helm/commonly/values-staging.yaml index c95b9ead99..a90a713d86 100644 --- a/k8s/helm/commonly/values-staging.yaml +++ b/k8s/helm/commonly/values-staging.yaml @@ -47,15 +47,6 @@ discord: email: enabled: false -# No prod GitHub PAT on staging. The shared commonly-github-pat has write power over -# prod repos, and staging auto-deploys every merge (the reckless lane), so the -# api-keys render must not reference it (Cofounder's ruling, same principle as -# keeping atlasAdmin off staging). The backend uses GitHub App installation tokens -# and does not read this key. If staging agents ever need git, that is a -# separate fine-grained PAT on a throwaway repo, post-11/1. -githubPat: - enabled: false - # Staging renders NO Ingress object. A staging Ingress would be picked up by the # single shared ingress-nginx controller and reload prod routing (R1). Staging # is reached only through its own cloudflared tunnel, straight to Services. @@ -87,25 +78,6 @@ cloudflared: operator: Equal value: spot -# clawdbot-gateway (openclaw moltbot) is OFF in staging. Prod has no clawdbot-gateway -# Deployment either — prod runs backend, commonly-bot, frontend, litellm and redis — so -# staging matches. Disabling it also drops its two PVCs (clawdbot-config / clawdbot- -# workspace, which never Bound in the first install attempt) and the gateway's CPU. -# Hosted agents answer through litellm (native runtime), not this gateway; if the Fri -# walk needs a live moltbot inside a staging pod, enable one right-sized replica. -agents: - clawdbot: - enabled: false - # The gateway Deployment/Service gate on `enabled`, but its two PVCs gate on - # these separate persistence flags (default true) — leaving them on would re-create - # the unbound clawdbot-config / clawdbot-workspace PVCs that stalled the first - # install (no consumer, never Bound). Off here so the gateway leaves nothing behind. - persistence: - config: - enabled: false - workspace: - enabled: false - backend: env: agentComputerEnabled: "true" diff --git a/k8s/helm/commonly/values.yaml b/k8s/helm/commonly/values.yaml index f1497542c2..37439e7f67 100644 --- a/k8s/helm/commonly/values.yaml +++ b/k8s/helm/commonly/values.yaml @@ -79,10 +79,9 @@ hostedMcp: googleCalendar: enabled: false -# Agent provisioning (RBAC + PVCs) +# Agent provisioning RBAC agentProvisioning: enabled: true - createWorkspacePvc: false # Backend service configuration backend: @@ -165,8 +164,6 @@ backend: # "native" to run new hosted installs in-process (TASK-261); empty keeps # the Durable Object. hostedEngine: "" - clawdbotGatewayUrl: "" - moltbotGatewayUrl: "" pgHost: "YOUR_PG_HOST" pgPort: "25450" pgDatabase: "defaultdb" @@ -320,51 +317,6 @@ externalSecrets: # Agent runtime configuration agents: - clawdbot: - enabled: true - strategy: - type: Recreate - image: - repository: gcr.io/YOUR_GCP_PROJECT_ID/clawdbot-gateway - tag: latest - pullPolicy: IfNotPresent - # ADR-005 Stage 2 — codex + @commonly/cli installed by an init - # container into a shared /tools volume. codexVersion pins the - # @openai/codex npm package; commonlyCliRef pins the git ref this - # repo's cli/ subdirectory is installed from. @commonly/cli is not - # yet on npm (ADR-005 Phase 4), so we install from source. - codexTools: - codexVersion: "0.125.0" - commonlyCliRef: "main" - config: - gatewayPort: 18789 - bridgePort: 18790 - gatewayBind: lan - persistence: - config: - enabled: true - storageClass: standard-rwo - size: 1Gi - accessMode: ReadWriteOnce - workspace: - enabled: true - storageClass: standard-rwo - size: 10Gi - accessMode: ReadWriteOnce - resources: - requests: - memory: "512Mi" - cpu: "200m" - limits: - memory: "2Gi" - cpu: "1000m" - securityContext: - runAsUser: 1000 - runAsGroup: 1000 - fsGroup: 1000 - nodeSelector: {} - tolerations: [] - commonlyBot: enabled: false image: @@ -447,14 +399,6 @@ discord: email: enabled: true -# Shared GitHub PAT (api-keys -> GITHUB_PAT), now consumed only by the clawdbot -# gateway (retires with ADR-021). The backend, ops-runner and cloud-codex no longer -# read it: backend GitHub calls use GitHub App installation tokens. On (default) -# for prod/dev. Off (staging) omits the ref entirely, so the auto-deploy-every-merge -# lane holds no credential with write power over prod repos. -githubPat: - enabled: true - # Ingress configuration ingress: enabled: true diff --git a/scripts/verify-retired-gateway-chart.sh b/scripts/verify-retired-gateway-chart.sh new file mode 100644 index 0000000000..03a1c44269 --- /dev/null +++ b/scripts/verify-retired-gateway-chart.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# ADR-021: old private overlays must not resurrect the retired gateway. +set -euo pipefail +cd "$(dirname "$0")/.." +task_tmp=$(mktemp -d) +trap 'rm -rf "$task_tmp"' EXIT +for lane in default dev staging local; do + values=(-f k8s/helm/commonly/values.yaml) + if [[ "$lane" != default ]]; then + values+=(-f "k8s/helm/commonly/values-$lane.yaml") + fi + helm template "commonly-$lane" k8s/helm/commonly "${values[@]}" \ + --set agents.clawdbot.enabled=true \ + --set agents.clawdbot.persistence.config.enabled=true \ + --set agents.clawdbot.persistence.workspace.enabled=true \ + --set agentProvisioning.createWorkspacePvc=true \ + --set githubPat.enabled=true > "$task_tmp/$lane.yaml" + if sed '/^[[:space:]]*#/d' "$task_tmp/$lane.yaml" | grep -Eiq '^[[:space:]-]*(name|claimName|app|image|secretKey|key):.*(clawdbot|filestore-sc|GITHUB_PAT|CLAWDBOT|MOLTBOT)' ; then + echo "Retired gateway resource or credential rendered in $lane" >&2 + exit 1 + fi + grep -q 'name: backend' "$task_tmp/$lane.yaml" + grep -q 'name: frontend' "$task_tmp/$lane.yaml" +done +# Deployment and test workflows must not fetch or build the retired runtime. +if grep -Eq 'submodules:|steps\.gateway|GATEWAY_TAG|verify:moltbot-tools|docker build _external/clawdbot' \ + .github/workflows/{deploy-dev,deploy-staging,tests}.yml; then + echo 'Retired gateway CI dependency remains' >&2 + exit 1 +fi +echo 'Gateway retirement: four chart lanes and three workflows pass' From 74f508cec4fb603cc3443daaa6fc66979a7e3741 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 04:25:24 -0700 Subject: [PATCH 2/2] test(deploy): cover retired storage references and remove obsolete CI assertion Co-authored-by: flint Agent-Model: codex/gpt-6.1-sol/medium --- .../unit/scripts/moltbotToolContract.test.js | 18 ++---------------- scripts/verify-retired-gateway-chart.sh | 10 +++++++++- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/backend/__tests__/unit/scripts/moltbotToolContract.test.js b/backend/__tests__/unit/scripts/moltbotToolContract.test.js index 433a501dd1..9802d5f011 100644 --- a/backend/__tests__/unit/scripts/moltbotToolContract.test.js +++ b/backend/__tests__/unit/scripts/moltbotToolContract.test.js @@ -471,22 +471,8 @@ describe('moltbot tool contract', () => { expect(exitTwo).toContain("direction.state === 'undetermined'"); }); - /** - * The other half of the CI repair, and the one a mocked exec cannot - * reach: the job that runs this guard has to check out deep enough for - * `origin/main` to exist. Without this the probe is correct and silent — - * it reports undetermined on every pull request and the direction check - * is, in practice, off. That was the live state until #2497. - */ - it('runs in a CI job whose checkout can reach origin/main', () => { - const workflow = repoFile('.github/workflows/tests.yml'); - const jobs = workflow.split(/\n {2}(?=[a-z][\w-]*:\n)/); - const job = jobs.find((block) => block.includes('verify:moltbot-tools')); - expect(job).toBeDefined(); - // Scoped to the job that actually runs the guard: a `fetch-depth` on - // some other job is exactly the thing that looks like a pass and isn't. - expect(job).toMatch(/fetch-depth:\s*0/); - }); + // The retired runtime no longer runs this guard in the CI workflow. + /** * A SHALLOW checkout answers this question confidently and wrongly. diff --git a/scripts/verify-retired-gateway-chart.sh b/scripts/verify-retired-gateway-chart.sh index 03a1c44269..dd1f009fe9 100644 --- a/scripts/verify-retired-gateway-chart.sh +++ b/scripts/verify-retired-gateway-chart.sh @@ -4,6 +4,14 @@ set -euo pipefail cd "$(dirname "$0")/.." task_tmp=$(mktemp -d) trap 'rm -rf "$task_tmp"' EXIT +has_retired_reference() { + sed '/^[[:space:]]*#/d' | grep -Eiq '^[[:space:]-]*(name|claimName|storageClassName|app|image|secretKey|key):.*(clawdbot|filestore-sc|GITHUB_PAT|CLAWDBOT|MOLTBOT)' +} +# Positive control: a surviving PVC reference must fail the same check. +if ! printf 'spec:\n storageClassName: filestore-sc\n' | has_retired_reference; then + echo 'Retirement detector missed the StorageClass control' >&2 + exit 1 +fi for lane in default dev staging local; do values=(-f k8s/helm/commonly/values.yaml) if [[ "$lane" != default ]]; then @@ -15,7 +23,7 @@ for lane in default dev staging local; do --set agents.clawdbot.persistence.workspace.enabled=true \ --set agentProvisioning.createWorkspacePvc=true \ --set githubPat.enabled=true > "$task_tmp/$lane.yaml" - if sed '/^[[:space:]]*#/d' "$task_tmp/$lane.yaml" | grep -Eiq '^[[:space:]-]*(name|claimName|app|image|secretKey|key):.*(clawdbot|filestore-sc|GITHUB_PAT|CLAWDBOT|MOLTBOT)' ; then + if has_retired_reference < "$task_tmp/$lane.yaml"; then echo "Retired gateway resource or credential rendered in $lane" >&2 exit 1 fi