Skip to content

chore: add weekly dependency maintenance sweep - #483

Merged
dodeja merged 5 commits into
mainfrom
claude/dependency-sweep
Oct 23, 2026
Merged

dodeja merged 5 commits into
mainfrom
claude/dependency-sweep

Conversation

@claude

@claude claude Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Akshay Dodeja · Slack thread

Before: Dependabot in this repo is security-only (open-pull-requests-limit: 0 for 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.yml runs Mondays 14:00 UTC (and on workflow_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, no id-token): checks out main without persisted credentials, skips if a claude/weekly-dependency-maintenance-* branch or same-repo PR already exists (REST only), sets npm ignore-scripts in the user config and npm_config_before (now minus 7 days) in the agent step, runs npm ci --ignore-scripts with no caching, then runs claude-code-action with github_token: ${{ github.token }}. The agent runs no repository or dependency code: agent-verify, sdk:docs and git checkout are gone (CI on the PR validates). Bash is limited to exact command strings (npm audit --audit-level=moderate, npm outdated ..., a few git status|diff forms) and a fixed sweep-npm wrapper written to $RUNNER_TEMP (age, update, install, reset: validated package names, x.y.z versions and workspaces, fixed flags, existing range style kept, no new dependencies, no URLs, no --ignore-scripts=false). Write is limited to pr-body.md and summary.md; .npmrc and .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 exports sweep.patch + pr-body.md as an artifact. The step summary shows the agent text only as inert text in a code fence.
  • publish (contents/pull-requests: write, no actions: write, no agent, no install): checks out the agent job's base_sha with full history, requires it to be an ancestor of origin/main, runs the verify script taken from origin/main against that base, applies the patch, pushes claude/weekly-dependency-maintenance-<run id> (safe to re-run), and opens the draft PR against main. 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.json must 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-0 are rejected) or goes down; no added or removed names; no change to version, scripts, overrides, engines or the @terminal49/sdk pins that release-please owns. In package-lock.json, workspace entries may differ only in such specifiers, every base link entry must survive unchanged, keys must look like [workspace/]node_modules/<name>[/node_modules/<name>] (no traversal), and the package name comes from the key (a name, inBundle, bundleDependencies or bin change is rejected). Every new or changed lockfile entry is checked against registry.npmjs.org (a failed lookup fails the check): dist.integrity and dist.tarball must equal integrity and resolved, 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.
  • Kept from the previous version of this PR: Node 24 and pinned action SHAs, the branch prefix, the policy on range style and exact pins and avoiding majors, the 7-day release-age rule (now enforced in code, not only in the prompt), and ANTHROPIC_API_KEY with CLAUDE_CODE_OAUTH_TOKEN as the fallback.

Residual risk (accepted, documented in the workflow header): the Anthropic credential reaches the claude-code-action step and the agent's own process can read it; CLAUDE_CODE_SUBPROCESS_ENV_SCRUB only 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_token so that exchange is skipped, and it avoids needing id-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.json and 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 real npm update + npm install style patch (accepted). Real npm update @types/node and npm install @types/[email protected] --workspace @terminal49/mcp patches were also accepted against the live registry with the default 7-day minimum. shellcheck is clean.
  • The sweep-npm wrapper 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 via npm_config_before.
  • The workflow's collect and publish run blocks were executed against throwaway repos with a local bare origin and a stub gh: 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 on main, hostile PR body text (links, images, <details>, closing keywords, owner/repo#N, fence break-out). actionlint (with shellcheck) and zizmor --offline report no findings.
  • Not tested: the claude-code-action step (including whether github_token really 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 manual workflow_dispatch after merge; the workflow needs the script at main, so it cannot be dispatched from this branch.

Needs an admin:

  • Add the repository secret ANTHROPIC_API_KEY (preferred: a dedicated key with a spend cap) or CLAUDE_CODE_OAUTH_TOKEN.
  • Enable Settings > Actions > General > "Allow GitHub Actions to create and approve pull requests", or the publish job pushes the branch but cannot open the PR (it then prints a compare link and fails).
  • Check rulesets: claude/* branch creation by the workflow token must be allowed, and required checks on main will stay pending until CI is started on the sweep PR.
  • CI does not start by itself: PRs and pushes made with GITHUB_TOKEN do not trigger workflows, ci.yml has no workflow_dispatch (not added here) and the publish job no longer has actions: write. A maintainer should close and reopen the sweep PR, or push to its branch, to run CI before merging.
  • Bumps that need overrides or source changes (vite-plus, vite/vitest overrides, @oxlint/plugins, @vitest/coverage-v8) cannot be made by the sweep and are reported as manual follow-ups.
  • Follow-up outside this PR: publish_typescript_cli.yml restores 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 remove cache: npm there.

Generated by Claude Code


&lt;source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3.svg"&gt;&lt;source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v3.svg"&gt;&lt;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"&gt; <a href="````''''''''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"&gt;&lt;picture&gt;&lt;source''''''''' media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"&gt;&lt;source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"&gt;&lt;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-bot with what you need.

@vercel

vercel Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
api Ready Ready Preview Oct 22, 2026 5:41am UTC

Request Review

@github-actions github-actions Bot added the risk-shadow:high Shadow-only risk signal: high label Oct 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Shadow risk calibration

Signal Result Authority
Path classifier risk:high Deterministic advisory policy
heuristic fallback risk-shadow:high Shadow only

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.

@github-actions github-actions Bot added the risk:unknown Advisory evidence unavailable or changed; no merge permission label Oct 22, 2026
@github-actions github-actions Bot added risk:high Advisory path risk; current-head human review needed and removed risk:unknown Advisory evidence unavailable or changed; no merge permission labels Oct 22, 2026
@claude claude Bot changed the title ci: add weekly dependency maintenance sweep chore: add weekly dependency maintenance sweep Oct 22, 2026
@github-actions github-actions Bot added risk:unknown Advisory evidence unavailable or changed; no merge permission and removed risk:high Advisory path risk; current-head human review needed labels Oct 22, 2026
@github-actions github-actions Bot added risk:high Advisory path risk; current-head human review needed and removed risk:unknown Advisory evidence unavailable or changed; no merge permission labels Oct 22, 2026
@dodeja
dodeja marked this pull request as ready for review October 22, 2026 04:13
@greptile-apps

greptile-apps Bot commented Oct 22, 2026

Copy link
Copy Markdown

PR author is in the excluded authors list.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T04:14:53.114795Z aa90db1 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@github-actions github-actions Bot added risk:unknown Advisory evidence unavailable or changed; no merge permission and removed risk:high Advisory path risk; current-head human review needed labels Oct 22, 2026
@github-actions github-actions Bot added risk:high Advisory path risk; current-head human review needed and removed risk:unknown Advisory evidence unavailable or changed; no merge permission labels Oct 22, 2026
@github-actions github-actions Bot added risk:unknown Advisory evidence unavailable or changed; no merge permission and removed risk:high Advisory path risk; current-head human review needed labels Oct 22, 2026
@github-actions github-actions Bot added risk:high Advisory path risk; current-head human review needed and removed risk:unknown Advisory evidence unavailable or changed; no merge permission labels Oct 22, 2026
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
@github-actions github-actions Bot added risk:unknown Advisory evidence unavailable or changed; no merge permission and removed risk:high Advisory path risk; current-head human review needed labels Oct 22, 2026
@github-actions github-actions Bot added risk:high Advisory path risk; current-head human review needed risk:unknown Advisory evidence unavailable or changed; no merge permission and removed risk:unknown Advisory evidence unavailable or changed; no merge permission risk:high Advisory path risk; current-head human review needed labels Oct 22, 2026
@dodeja
dodeja merged commit 29de6a6 into main Oct 23, 2026
24 of 25 checks passed
@dodeja
dodeja deleted the claude/dependency-sweep branch October 23, 2026 04:42

This branch was successfully deployed

1 active deployment
Preview — 21e7906b Deployed Oct 22, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:high Advisory path risk; current-head human review needed risk-shadow:high Shadow-only risk signal: high

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants