Repository navigation
chore: add weekly dependency maintenance sweep - #483
Conversation
Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Pg4JdrMxy6q2hdvmuPnKyR
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Shadow risk calibration
Calibration result: agreement. Shadow risk never blocks or enables merge. Keep it in observation mode until roughly 50 pull requests have human-reviewed labels, then calibrate before considering any policy use. |
Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Pg4JdrMxy6q2hdvmuPnKyR
|
PR author is in the excluded authors list. |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aa90db1e1e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| cache-dependency-path: package-lock.json | ||
|
|
||
| - name: Install workspace dependencies | ||
| run: npm ci |
There was a problem hiding this comment.
Disable lifecycle scripts in the initial install
When a dependency update containing a lifecycle script is merged, the next weekly run executes that script during this unrestricted npm ci, before the later validation install applies npm_config_ignore_scripts=true. This defeats the workflow's stated requirement that every npm install command suppress lifecycle scripts; npm documents that ignore-scripts=true prevents package scripts from running. Apply the same setting to this initial install so newly updated dependencies cannot execute install hooks.
Useful? React with 👍 / 👎.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Pg4JdrMxy6q2hdvmuPnKyR
Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Pg4JdrMxy6q2hdvmuPnKyR
Agent job: the agent no longer runs agent-verify, sdk:docs or git checkout, so no dependency code runs next to the Anthropic credential. Bash is limited to exact git/npm command strings and a fixed sweep-npm wrapper (validated names, versions and workspaces, fixed flags, range style kept); Write is limited to the two output files, with .npmrc and .git denied. npm ignore-scripts is set in the user config and npm_config_before (now minus 7 days) in the step environment. Agent text is scanned for credential-shaped strings and shown as inert text. Residual risk and the dedicated spend-capped key recommendation are documented. Publish job: the base commit must be an ancestor of origin/main and the verify script is taken from origin/main. The PR body is built from fixed notes, the verify script's list of changes and the agent text in a labelled code fence. actions: write and the ci.yml dispatch are removed. Verify script: every new or changed lockfile entry is checked against the npm registry (integrity, tarball, 7 day minimum age, declared dependencies, install scripts, bin), the package name comes from the key, name/inBundle/ bundleDependencies/bin changes are rejected, new package names must be declared by a verified manifest, workspace links must survive, keys are validated, and package.json specifiers must keep their style and not widen. Duplicate keys in package.json are rejected. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Pg4JdrMxy6q2hdvmuPnKyR
Requested by Akshay Dodeja · Slack thread
Before: Dependabot in this repo is security-only (
open-pull-requests-limit: 0for npm and GitHub Actions), so routine patch/minor bumps of the npm workspace (MCP server, SDK, CLI) never get proposed.After: A weekly workflow has Claude audit the npm workspace and open at most one draft PR with safe security and patch/minor updates. The agent that runs over freshly published third-party code never holds a credential that can write to the repository.
How:
.github/workflows/weekly-dependency-maintenance.ymlruns Mondays 14:00 UTC (and onworkflow_dispatch) as two jobs, ported from the design reviewed in the Terminal49/t49 sweep and hardened after an Opus review of this PR:agent(contents: read,pull-requests: read, noid-token): checks outmainwithout persisted credentials, skips if aclaude/weekly-dependency-maintenance-*branch or same-repo PR already exists (REST only), sets npmignore-scriptsin the user config andnpm_config_before(now minus 7 days) in the agent step, runsnpm ci --ignore-scriptswith no caching, then runsclaude-code-actionwithgithub_token: ${{ github.token }}. The agent runs no repository or dependency code:agent-verify,sdk:docsandgit checkoutare gone (CI on the PR validates). Bash is limited to exact command strings (npm audit --audit-level=moderate,npm outdated ..., a fewgit status|diffforms) and a fixedsweep-npmwrapper written to$RUNNER_TEMP(age,update,install,reset: validated package names,x.y.zversions and workspaces, fixed flags, existing range style kept, no new dependencies, no URLs, no--ignore-scripts=false). Write is limited topr-body.mdandsummary.md;.npmrcand.git/**are denied. A deterministic step scans the agent text for credential-shaped strings, fails on anything outside the manifest/lockfile allowlist, runs the verify script and exportssweep.patch+pr-body.mdas an artifact. The step summary shows the agent text only as inert text in a code fence.publish(contents/pull-requests: write, noactions: write, no agent, no install): checks out the agent job'sbase_shawith full history, requires it to be an ancestor oforigin/main, runs the verify script taken fromorigin/mainagainst that base, applies the patch, pushesclaude/weekly-dependency-maintenance-<run id>(safe to re-run), and opens the draft PR againstmain. The PR body is built by the workflow: fixed notes first, then the verify script's own list of changed, updated and new packages (validated names and versions only), then the agent text in a labelled code fence (control characters dropped,@,#<digits>,://and closing keywords broken up, credential-shaped text aborts the job, size capped).scripts/ci/verify-dependency-sweep-patch.sh: only the five manifest/lockfile paths may change, as modifications of existing files.package.jsonmust parse without duplicate keys, and only dependency specifiers may change: each keeps its style (single plain version with the same^,~or none), never widens (*,>=0,4.3.6 || >=0.0.0-0are rejected) or goes down; no added or removed names; no change toversion,scripts,overrides,enginesor the@terminal49/sdkpins that release-please owns. Inpackage-lock.json, workspace entries may differ only in such specifiers, every baselinkentry must survive unchanged, keys must look like[workspace/]node_modules/<name>[/node_modules/<name>](no traversal), and the package name comes from the key (aname,inBundle,bundleDependenciesorbinchange is rejected). Every new or changed lockfile entry is checked against registry.npmjs.org (a failed lookup fails the check):dist.integrityanddist.tarballmust equalintegrityandresolved, a new version must be at least 7 days old (no exception for security fixes; those become manual follow-ups), no install script that the base entry lacked, dependency lists must match the registry manifest, and a package name that is new to the lockfile must be declared as a dependency by a verified manifest, an unchanged base entry or a workspace.ANTHROPIC_API_KEYwithCLAUDE_CODE_OAUTH_TOKENas the fallback.Residual risk (accepted, documented in the workflow header): the Anthropic credential reaches the
claude-code-actionstep and the agent's own process can read it;CLAUDE_CODE_SUBPROCESS_ENV_SCRUBonly strips child environments. The review's main escalation (third-party code reading the parent's/proc/<pid>/environ) is removed because no such code runs, and the agent's Bash tool can no longer read arbitrary files. A determined prompt injection that gets the agent to encode the key into its text output could still leak it, hence the credential scan and inert rendering above. Use a dedicated Anthropic API key with a spend cap for this workflow, not a shared key and not a personal OAuth token (the OAuth token stays only as the fallback).Why two jobs: the earlier single-job version used the action's OIDC app-token exchange. In Terminal49/t49 the same pattern failed on 23 of 23 scheduled runs at the "App token exchange failed: 400 Bad Request" step (root cause not confirmed). This design supplies
github_tokenso that exchange is skipped, and it avoids needingid-token: write. Whether it works here is unproven (see Testing).Testing:
scripts/ci/verify-dependency-sweep-patch.sh: 174 pass/fail cases in throwaway git repos built from this repo's real manifests and lockfile against a local stub registry (paths, creates/deletes/renames/mode/binary,package.jsonand lockfile mutations, loosened or restyled ranges, duplicate keys, workspace version and pin changes, link swaps,name/inBundle/bundleDependencies/bin, key traversal, undeclared new packages, registry integrity/tarball/age/install-script/dependency mismatches, 404 and unreachable registry), plus 25 crafted patches from the review (each rejected, in a mode where the registry agrees with the patch and in one where it is truthful) and a realnpm update+npm installstyle patch (accepted). Realnpm update @types/nodeandnpm install @types/[email protected] --workspace @terminal49/mcppatches were also accepted against the live registry with the default 7-day minimum.shellcheckis clean.sweep-npmwrapper was run with a real npm 11 / Node 24: argument validation,age,install(range style kept),update,reset, and npm refusing versions younger than 7 days vianpm_config_before.runblocks were executed against throwaway repos with a local bare origin and a stubgh: no change, valid change, untracked/staged/edited-script/moved-HEAD rejections, credential-shaped agent text, first run, re-run, divergent existing branch, PR creation failure, base commit that is not onmain, hostile PR body text (links, images,<details>, closing keywords,owner/repo#N, fence break-out).actionlint(with shellcheck) andzizmor --offlinereport no findings.claude-code-actionstep (including whethergithub_tokenreally skips the OIDC exchange and whether the exact-string Bash and Write patterns match the commands the prompt names), the upload/download-artifact pins, real PR creation, and a live new-transitive-package update (none was available in the 7-day window; that path is covered with the stub registry only). Run a manualworkflow_dispatchafter merge; the workflow needs the script atmain, so it cannot be dispatched from this branch.Needs an admin:
ANTHROPIC_API_KEY(preferred: a dedicated key with a spend cap) orCLAUDE_CODE_OAUTH_TOKEN.claude/*branch creation by the workflow token must be allowed, and required checks onmainwill stay pending until CI is started on the sweep PR.GITHUB_TOKENdo not trigger workflows,ci.ymlhas noworkflow_dispatch(not added here) and the publish job no longer hasactions: write. A maintainer should close and reopen the sweep PR, or push to its branch, to run CI before merging.overridesor source changes (vite-plus,vite/vitestoverrides,@oxlint/plugins,@vitest/coverage-v8) cannot be made by the sweep and are reported as manual follow-ups.publish_typescript_cli.ymlrestores the npm cache (cache: npm) in a job that publishes to npm. A cache entry written from the main scope is trusted by lockfile hash, so removecache: npmthere.Generated by Claude Code
<source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v3.svg"><img alt="View with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png" width="162" height="28">'''''https://backend.blacksmith.sh/track/enable-autofix?expires=1795233394&installation_model_id=18852&pr_number=483&ref=codesmith_pr_footer&repository=Terminal49%2FAPI&return_to=https%3A%2F%2Fgithub.com%2FTerminal49%2FAPI%2Fpull%2F483&signature=c757526a4e8ca87e798fa96205b1f9c4af050cab350954013ba6b52ed0736632"><picture><source'''''''''media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img alt="Autofix with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png" width="190" height="28">```Need help on this PR? Tag
@codesmith-botwith what you need.