diff --git a/.github/claude-cli/package-lock.json b/.github/claude-cli/package-lock.json new file mode 100644 index 0000000000..8ab85210e9 --- /dev/null +++ b/.github/claude-cli/package-lock.json @@ -0,0 +1,152 @@ +{ + "name": "clawmetry-ci-claude-cli", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "clawmetry-ci-claude-cli", + "version": "1.0.0", + "license": "UNLICENSED", + "devDependencies": { + "@anthropic-ai/claude-code": "2.1.251" + } + }, + "node_modules/@anthropic-ai/claude-code": { + "version": "2.1.251", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-2.1.251.tgz", + "integrity": "sha512-eG+ZPPpW2Dbmnntf1Fz9/T9ewS8I8SKfc1tcU2PqSwmftfjRPP7BXPaCyLuZ8kvgTdiPnJi/2/JnTvTRieneEQ==", + "dev": true, + "hasInstallScript": true, + "license": "SEE LICENSE IN README.md", + "bin": { + "claude": "bin/claude.exe" + }, + "engines": { + "node": ">=22.0.0" + }, + "optionalDependencies": { + "@anthropic-ai/claude-code-darwin-arm64": "2.1.251", + "@anthropic-ai/claude-code-darwin-x64": "2.1.251", + "@anthropic-ai/claude-code-linux-arm64": "2.1.251", + "@anthropic-ai/claude-code-linux-arm64-musl": "2.1.251", + "@anthropic-ai/claude-code-linux-x64": "2.1.251", + "@anthropic-ai/claude-code-linux-x64-musl": "2.1.251", + "@anthropic-ai/claude-code-win32-arm64": "2.1.251", + "@anthropic-ai/claude-code-win32-x64": "2.1.251" + } + }, + "node_modules/@anthropic-ai/claude-code-darwin-arm64": { + "version": "2.1.251", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-darwin-arm64/-/claude-code-darwin-arm64-2.1.251.tgz", + "integrity": "sha512-Qr5oMGVrOUyatsMlK0361OSnr3C785QBFIDoaiHMpaJ/nu/Ji2ccwI7nv0o54q3v3Y+zU9xbtEmcGxPRcR9ptA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@anthropic-ai/claude-code-darwin-x64": { + "version": "2.1.251", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-darwin-x64/-/claude-code-darwin-x64-2.1.251.tgz", + "integrity": "sha512-wDMj6uELqU/uUeXH3E+32R9Gyx6pxdffBmjCbP8CoGGN0VufEd88vXhIGK2P0XIsksSh3fJZdxS00EaQSpHYPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@anthropic-ai/claude-code-linux-arm64": { + "version": "2.1.251", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-linux-arm64/-/claude-code-linux-arm64-2.1.251.tgz", + "integrity": "sha512-h4EXRyV25yJfez40cTIoBlJptJqccP8nIDPdDMO9TDPUzZ6e5Y3OO9AgrfdMRAgccaTRsKfJenh7lA2D40nzdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@anthropic-ai/claude-code-linux-arm64-musl": { + "version": "2.1.251", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-linux-arm64-musl/-/claude-code-linux-arm64-musl-2.1.251.tgz", + "integrity": "sha512-VJAZGuGTaRQINdUS35qhkgZoK8R+0MciPHMG6KMTFHjLC8GKtxj47RmiWcCUAu+fJpEOsko0FLEBqvAbdOpkJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@anthropic-ai/claude-code-linux-x64": { + "version": "2.1.251", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-linux-x64/-/claude-code-linux-x64-2.1.251.tgz", + "integrity": "sha512-HJyCY1ynzlsBk+N02IJeBNNZmzyd43lMuff49IXtbUDGHlf2XFHcxwYJEWCwIW51J3Hl4MvrqM6Ye8PGpJRIiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@anthropic-ai/claude-code-linux-x64-musl": { + "version": "2.1.251", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-linux-x64-musl/-/claude-code-linux-x64-musl-2.1.251.tgz", + "integrity": "sha512-fuELyER/KyDie/iPimqHgZauPINBb+mWoYG7dXKxaxXlqCSmSI9YHzDVQ/1zSfiUnAU3FhbT1p9GNBqwJPikqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@anthropic-ai/claude-code-win32-arm64": { + "version": "2.1.251", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-win32-arm64/-/claude-code-win32-arm64-2.1.251.tgz", + "integrity": "sha512-6hkf7WoAk74WJuQ/epE+GKy5SJ7RU7kcpy3PRFHt6E1tiYNscihfZY1TMQ+AMQsDo1iFQFHbGITR95+vr3at+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@anthropic-ai/claude-code-win32-x64": { + "version": "2.1.251", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-win32-x64/-/claude-code-win32-x64-2.1.251.tgz", + "integrity": "sha512-fVXAvS2lCMJWD/lcyzzai5pcDQnlldGl8pwyGQ2vBxcuF8LS/7nVDqLOqTZsoAJ+VDKnlkPlPOJytDQEhTHHMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "win32" + ] + } + } +} diff --git a/.github/claude-cli/package.json b/.github/claude-cli/package.json new file mode 100644 index 0000000000..1d8344ab6c --- /dev/null +++ b/.github/claude-cli/package.json @@ -0,0 +1,10 @@ +{ + "name": "clawmetry-ci-claude-cli", + "version": "1.0.0", + "private": true, + "description": "Pinned Claude Code CLI for the CI workflows that shell out to `claude`.", + "license": "UNLICENSED", + "devDependencies": { + "@anthropic-ai/claude-code": "2.1.251" + } +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 64aafef45c..632122c146 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -92,6 +92,23 @@ updates: cooldown: default-days: 7 + # npm — the Claude Code CLI that CI workflows shell out to + # (harness-observability-audit, i18n-autotranslate, i18n-docs-autotranslate). + # Those jobs install it with `npm ci` from a committed lockfile, so without an + # updater here the pin would simply freeze. This is the same pin/updater + # pairing described above for the SHA pins: the lock makes an upgrade + # deliberate, this entry keeps it from going stale. + - package-ecosystem: "npm" + directory: "/.github/claude-cli" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 3 + labels: + - "dependencies" + cooldown: + default-days: 7 + # npm — the ClawHub/OpenClaw plugin, which is published to npm # (openclaw.release.publishToNpm). No lockfile, so Dependabot updates the # manifest ranges. diff --git a/.github/workflows/harness-observability-audit.yml b/.github/workflows/harness-observability-audit.yml index 73e06d6d3a..fc60b91076 100644 --- a/.github/workflows/harness-observability-audit.yml +++ b/.github/workflows/harness-observability-audit.yml @@ -60,8 +60,23 @@ jobs: with: node-version: "22" - - name: Install Claude Code CLI - run: npm install -g @anthropic-ai/claude-code + # Installed from a committed lockfile rather than the registry's moving + # `latest`. This job holds `issues: write` and the CLAUDE_CODE_OAUTH_TOKEN + # secret, so whatever this step installs runs with both — and + # `npm install -g @anthropic-ai/claude-code` took whatever the tag pointed + # at the moment the job ran, which is the window a compromised release is + # published into. + # + # `npm ci` installs exactly the tree recorded in + # .github/claude-cli/package-lock.json and verifies every tarball against + # the integrity hash stored beside it. A bare `@version` pin does neither: + # it re-resolves transitive deps on each run and checks nothing. The npm + # entry in .github/dependabot.yml is what keeps the lock from going stale. + - name: Install Claude Code CLI (from lockfile) + working-directory: .github/claude-cli + run: | + npm ci --no-audit --no-fund --silent + echo "$PWD/node_modules/.bin" >> "$GITHUB_PATH" - name: Token visibility (presence only) run: | diff --git a/.github/workflows/i18n-autotranslate.yml b/.github/workflows/i18n-autotranslate.yml index dff2734975..74e636f7ea 100644 --- a/.github/workflows/i18n-autotranslate.yml +++ b/.github/workflows/i18n-autotranslate.yml @@ -44,8 +44,9 @@ jobs: # so it never reads this one. The script below only runs # `git show :`, a local object read needing no credential. # Left persisted, a `contents: write` token would instead sit - # readable in .git/config for the whole job, which installs a global - # npm package and runs the Claude Code CLI over repository content. + # readable in .git/config for the whole job, which installs a + # third-party npm tree and runs the Claude Code CLI over repository + # content. persist-credentials: false - name: Guard - require Claude Code token @@ -60,9 +61,32 @@ jobs: with: python-version: '3.11' - - name: Install Claude Code CLI + # The pinned CLI declares `engines: node >=22`, and this job previously + # relied on whatever Node the runner image happened to ship. Pinning the + # toolchain here keeps `npm ci` reproducible rather than image-dependent. + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 if: env.SKIP != '1' - run: npm install -g @anthropic-ai/claude-code + with: + node-version: "22" + + # Installed from a committed lockfile rather than the registry's moving + # `latest`. This job holds `contents: write`, `pull-requests: write` and + # the CLAUDE_CODE_OAUTH_TOKEN secret, so what this step installs runs with + # both — and `npm install -g @anthropic-ai/claude-code` took whatever the + # tag pointed at the moment the job ran, which is the window a compromised + # release is published into. + # + # `npm ci` installs exactly the tree recorded in + # .github/claude-cli/package-lock.json and verifies every tarball against + # the integrity hash stored beside it. A bare `@version` pin does neither: + # it re-resolves transitive deps on each run and checks nothing. The npm + # entry in .github/dependabot.yml is what keeps the lock from going stale. + - name: Install Claude Code CLI (from lockfile) + if: env.SKIP != '1' + working-directory: .github/claude-cli + run: | + npm ci --no-audit --no-fund --silent + echo "$PWD/node_modules/.bin" >> "$GITHUB_PATH" - name: Translate deltas (via Claude Code CLI, no API key) if: env.SKIP != '1' diff --git a/.github/workflows/i18n-docs-autotranslate.yml b/.github/workflows/i18n-docs-autotranslate.yml index 0ad544ba10..14c9691670 100644 --- a/.github/workflows/i18n-docs-autotranslate.yml +++ b/.github/workflows/i18n-docs-autotranslate.yml @@ -52,9 +52,32 @@ jobs: with: python-version: '3.11' - - name: Install Claude Code CLI + # The pinned CLI declares `engines: node >=22`, and this job previously + # relied on whatever Node the runner image happened to ship. Pinning the + # toolchain here keeps `npm ci` reproducible rather than image-dependent. + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 if: env.SKIP != '1' - run: npm install -g @anthropic-ai/claude-code + with: + node-version: "22" + + # Installed from a committed lockfile rather than the registry's moving + # `latest`. This job holds `contents: write`, `pull-requests: write` and + # the CLAUDE_CODE_OAUTH_TOKEN secret, so what this step installs runs with + # both — and `npm install -g @anthropic-ai/claude-code` took whatever the + # tag pointed at the moment the job ran, which is the window a compromised + # release is published into. + # + # `npm ci` installs exactly the tree recorded in + # .github/claude-cli/package-lock.json and verifies every tarball against + # the integrity hash stored beside it. A bare `@version` pin does neither: + # it re-resolves transitive deps on each run and checks nothing. The npm + # entry in .github/dependabot.yml is what keeps the lock from going stale. + - name: Install Claude Code CLI (from lockfile) + if: env.SKIP != '1' + working-directory: .github/claude-cli + run: | + npm ci --no-audit --no-fund --silent + echo "$PWD/node_modules/.bin" >> "$GITHUB_PATH" - name: Translate README into all locales if: env.SKIP != '1'