Files
getsentry__sentry-for-ai/.github/skill-drift-matrix.json
Daniel Griesser 3c78b7064c feat(skill-drift): replace gh-aw workflow with matrix-fanout Claude runs (#135)
* feat(skill-drift): replace gh-aw workflow with matrix-fanout Claude runs

Replaces .github/workflows/skill-drift-check.{md,lock.yml} (single
sequential gh-aw run) with a plain GitHub Actions workflow that fans out
one Claude Sonnet session per SDK skill via matrix strategy.

Each per-skill job reads the last 7 days of merged PRs in the
corresponding SDK repo, opens the actually changed source files at the
merge SHA (not just the diff summary), compares against the local skill
bundle, and emits either a file diff under skills/<skill>/ or a
structured summary for the apply job to act on.

Security
- All actions pinned to a full commit SHA.
- permissions: {} at workflow root; each job grants only what it needs.
- The agent job (detect) runs with contents: read ONLY — no write tokens
  reachable from the LLM session.
- Agent tool allowlist restricts Bash to a handful of gh/jq/date
  subcommands; no git, curl, or arbitrary shell.
- The apply job is pure deterministic shell with a path allowlist
  (skills/<this-skill>/ only) enforced before any commit, push, or PR.
- Inline reviewer assignment in the apply job since PRs opened by
  GITHUB_TOKEN do not trigger downstream workflow_run events.
- Trigger surface is schedule + workflow_dispatch only.

Matrix lives in .github/skill-drift-matrix.json so adding a new SDK skill
is a one-line change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(skill-drift): address review feedback in apply job

Four issues flagged on PR #135:

1. (Cursor Bugbot, HIGH) The `artifacts/` directory placed by
   download-artifact was being picked up by `git ls-files --others
   --exclude-standard` in the apply job's path-allowlist check, which
   would have downgraded EVERY create_pr to an issue because no
   `artifacts/...` path matches the `^skills/<skill>/` regex. Scope
   both `git diff` and `git ls-files` to `-- skills/` so anything
   outside that subtree (artifacts/, dotfiles, etc.) is invisible to
   the allowlist scan.

2. (Sentry Bot, HIGH) On the path-allowlist and empty-diff bail-out
   paths the script switched branches but did not remove the untracked
   files left behind by `rsync`. The next iteration would then see
   those files via `git ls-files --others` and trigger a false-positive
   allowlist violation, cascading the failure across all remaining
   skills. Add `git clean -fd -- skills/` at the start of each
   create_pr iteration and on both bail-out paths.

3. (Cursor Bugbot, MEDIUM) `git diff --quiet HEAD` does not detect
   brand-new untracked files. If the agent's only change was adding a
   new `references/*.md`, the empty-diff guard would have silently
   skipped the PR. Replace with an `[[ -z "$TOUCHED" ]]` check, since
   TOUCHED already includes untracked files.

4. (Sentry Bot, MEDIUM) Parsing the PR number from `gh pr create`
   stdout via `basename` is fragile — extra text would corrupt it and
   the downstream JS reviewer-assignment step would silently skip.
   Look up the number via `gh pr view "$BRANCH" --json number --jq
   .number` instead, and log a `::warning::` (plus increment FAILED)
   if it still cannot be determined. Also add a warning log in the JS
   step when an invalid PR number is encountered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(skill-drift): second review pass — length caps, retries, pagination, edge cases

Addresses the round of review comments left on commit fea5d4a:

1. (sentry-warden, security) Untrusted SDK PR content reaches issue/PR
   bodies via `gh issue create --body "$BODY"` and `gh pr create --body`.
   Add a 256-char cap on `title` and a 10000-char cap on `body`, enforced
   in both detect's validate step and apply's re-validation. Prepend a
   GitHub `> [!WARNING]` alert header to every issue/PR body the agent
   produces so human reviewers see "Auto-generated from external SDK
   content. Review all links and code suggestions before acting on
   them." before reading agent-authored text. Limits the blast radius
   of a successful prompt-injection from an SDK PR.

2. (sentry-bot, MEDIUM) Race condition between `gh pr create` and
   `gh pr view`. GitHub's API is eventually consistent — the immediate
   view can 404 and silently skip reviewer assignment. Retry up to 5
   times with a 1s sleep; warn (not silently skip) if all attempts fail.

3. (cursor-bot, MEDIUM) `actions/download-artifact@v4` fails the step
   when its pattern matches zero artifacts, before the apply script's
   graceful empty-artifact branch can run. Mark the download step
   `continue-on-error: true`. The apply script already handles a
   missing `artifacts/` directory and now also emits a complete set of
   step-outputs (prs/issues/no_drift/failed/opened_pr_nums) on the
   no-artifacts early-exit so downstream conditions see consistent
   values.

4. (cursor-bot, LOW) `github.rest.pulls.listFiles` in the inline
   reviewer-assignment step only returns the first page. Use
   `github.paginate(..., per_page: 100)` so PRs with many files don't
   miss teams. Matches the convention used by
   skill-drift-assign-reviewers.yml.

5. (cursor-bot, LOW) Trailing/leading/double commas in the
   `workflow_dispatch` `skills` input parsed as empty tokens, which
   then failed the unknown-skill check. Drop empty entries in the jq
   pipeline.

6. (cursor-bot, MEDIUM) `title` and `body` were validated as strings
   but empty strings passed, which would have broken `git commit -m ""`
   and yielded useless empty issues. Require non-empty values for
   `create_pr`/`create_issue` (still allowed for `no_drift`, which the
   apply job ignores entirely).

WONTFIX: the "hardcoded `main`" comment. This workflow is pinned to
`getsentry/sentry-for-ai` via `if: github.repository ==` guards on
every job; the default branch IS `main`. Parameterizing
`github.event.repository.default_branch` adds complexity for a
portability story we don't have.

Also: update the agent prompt with the new length caps and non-empty
requirement so the agent's output isn't silently rejected by
validation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 10:04:05 +02:00

117 lines
3.3 KiB
JSON

[
{
"skill": "sentry-android-sdk",
"repo": "getsentry/sentry-android",
"path_filter": "",
"team": "getsentry/team-mobile"
},
{
"skill": "sentry-browser-sdk",
"repo": "getsentry/sentry-javascript",
"path_filter": "packages/browser/ packages/core/",
"team": "getsentry/team-javascript-sdks"
},
{
"skill": "sentry-cloudflare-sdk",
"repo": "getsentry/sentry-javascript",
"path_filter": "packages/cloudflare/ packages/core/",
"team": "getsentry/team-javascript-sdks"
},
{
"skill": "sentry-cocoa-sdk",
"repo": "getsentry/sentry-cocoa",
"path_filter": "",
"team": "getsentry/team-mobile"
},
{
"skill": "sentry-dotnet-sdk",
"repo": "getsentry/sentry-dotnet",
"path_filter": "",
"team": "getsentry/team-web-sdk-backend"
},
{
"skill": "sentry-elixir-sdk",
"repo": "getsentry/sentry-elixir",
"path_filter": "",
"team": "getsentry/team-web-sdk-backend"
},
{
"skill": "sentry-flutter-sdk",
"repo": "getsentry/sentry-dart",
"path_filter": "",
"team": "getsentry/team-mobile-cross-platform"
},
{
"skill": "sentry-go-sdk",
"repo": "getsentry/sentry-go",
"path_filter": "",
"team": "getsentry/team-web-sdk-backend"
},
{
"skill": "sentry-nestjs-sdk",
"repo": "getsentry/sentry-javascript",
"path_filter": "packages/nestjs/ packages/node/ packages/core/",
"team": "getsentry/team-javascript-sdks"
},
{
"skill": "sentry-nextjs-sdk",
"repo": "getsentry/sentry-javascript",
"path_filter": "packages/nextjs/ packages/node/ packages/react/ packages/core/",
"team": "getsentry/team-javascript-sdks"
},
{
"skill": "sentry-node-sdk",
"repo": "getsentry/sentry-javascript",
"path_filter": "packages/node/ packages/bun/ packages/deno/ packages/core/",
"team": "getsentry/team-javascript-sdks"
},
{
"skill": "sentry-php-sdk",
"repo": "getsentry/sentry-php",
"path_filter": "",
"team": "getsentry/team-web-sdk-backend"
},
{
"skill": "sentry-python-sdk",
"repo": "getsentry/sentry-python",
"path_filter": "",
"team": "getsentry/owners-python-sdk"
},
{
"skill": "sentry-react-native-sdk",
"repo": "getsentry/sentry-react-native",
"path_filter": "",
"team": "getsentry/team-mobile-cross-platform"
},
{
"skill": "sentry-react-router-framework-sdk",
"repo": "getsentry/sentry-javascript",
"path_filter": "packages/react-router/ packages/profiling-node/ packages/core/",
"team": "getsentry/team-javascript-sdks"
},
{
"skill": "sentry-react-sdk",
"repo": "getsentry/sentry-javascript",
"path_filter": "packages/react/ packages/browser/ packages/core/",
"team": "getsentry/team-javascript-sdks"
},
{
"skill": "sentry-ruby-sdk",
"repo": "getsentry/sentry-ruby",
"path_filter": "",
"team": "getsentry/team-web-sdk-backend"
},
{
"skill": "sentry-svelte-sdk",
"repo": "getsentry/sentry-javascript",
"path_filter": "packages/svelte/ packages/sveltekit/ packages/browser/ packages/core/",
"team": "getsentry/team-javascript-sdks"
},
{
"skill": "sentry-tanstack-start-sdk",
"repo": "getsentry/sentry-javascript",
"path_filter": "packages/tanstackstart-react/ packages/core/",
"team": "getsentry/team-javascript-sdks"
}
]