Skip to content

Add project automation workflows#424

Closed
BillChirico wants to merge 1 commit intomainfrom
workflows-setup
Closed

Add project automation workflows#424
BillChirico wants to merge 1 commit intomainfrom
workflows-setup

Conversation

@BillChirico
Copy link
Member

Automation Workflows

This PR adds GitHub Actions workflows for the Sobers v2 project:

Workflows Added

  1. project-auto-progress.yml - Auto-move issues to "In Progress" when assigned or when PR opened
  2. auto-label-issues.yml - Auto-label new issues based on title keywords (bug, feat, docs, test)
  3. epic-subtask-sync.yml - Sync epic status when sub-tasks are closed/reopened
  4. stale-issue-management.yml - Weekly stale issue cleanup (30 days to stale, 7 days to close)

Project Integration

Part of #412

- Auto-move to In Progress when assigned
- Auto-label issues based on title keywords
- Epic sub-task status sync
- Stale issue management (weekly)
Copilot AI review requested due to automatic review settings March 11, 2026 14:58
@vercel
Copy link

vercel bot commented Mar 11, 2026

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

Project Deployment Actions Updated (UTC)
sobers Ready Ready Preview, Comment Mar 11, 2026 3:10pm

Request Review

@claude
Copy link
Contributor

claude bot commented Mar 11, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 11, 2026

📝 Walkthrough

Summary by CodeRabbit

  • Chores
    • Enhanced issue management with automated labeling based on issue content.
    • Improved project tracking synchronization between related issues and epics.
    • Added automatic project status updates based on issue activity.
    • Implemented stale issue detection to maintain repository hygiene by automatically managing inactive issues.

Walkthrough

Introduces four new GitHub Actions workflows to automate repository management tasks: auto-labeling issues based on keywords, syncing epic and subtask statuses, updating project progress on issue state changes, and managing stale issues and pull requests via scheduled cleanup.

Changes

Cohort / File(s) Summary
Issue Auto-Labeling
.github/workflows/auto-label-issues.yml
Automatically assigns labels (bug, enhancement, documentation, testing) to newly opened issues based on lowercase title keyword matching.
Epic-Subtask Synchronization
.github/workflows/epic-subtask-sync.yml
Posts comments on parent epics when child issues are closed or reopened, tracking subtask completion status.
Project Auto-Progress
.github/workflows/project-auto-progress.yml
Updates GitHub Project V2 item status to "In Progress" when issues are assigned or pull requests target main/develop branches.
Stale Issue Management
.github/workflows/stale-issue-management.yml
Automatically marks inactive issues and PRs as stale after 30 days and closes them after 7 additional days, with exemptions for labeled items (epic, in-progress, priority-high for issues; in-review for PRs).
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add project automation workflows' accurately and clearly summarizes the main change—adding four GitHub Actions automation workflows for the project.
Description check ✅ Passed The description is well-detailed and directly related to the changeset, providing context about all four workflows added and their purposes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch workflows-setup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment on lines +8 to +34
runs-on: ubuntu-latest
steps:
- name: Label based on title
uses: actions/github-script@v7
with:
script: |
const title = context.payload.issue.title.toLowerCase();
const labels = [];

if (title.includes('bug') || title.includes('fix') || title.includes('crash')) {
labels.push('bug');
} else if (title.includes('feat') || title.includes('add') || title.includes('implement')) {
labels.push('enhancement');
} else if (title.includes('docs') || title.includes('documentation')) {
labels.push('documentation');
} else if (title.includes('test') || title.includes('testing')) {
labels.push('testing');
}

if (labels.length > 0) {
github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: labels
});
}

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI 7 days ago

To fix the problem, explicitly define a permissions block for the job (or at the workflow root) that grants only the scopes required. This workflow only needs to read the issue (title) and add labels. Reading the issue is covered by issues: read or broader contents: read, and adding labels requires issues: write. No other scopes (e.g., contents: write) are necessary.

The best minimal fix, without changing functionality, is to add a permissions section under the auto-label job (line 7) specifying contents: read and issues: write. Placing it at the job level keeps the change tightly scoped. Concretely, in .github/workflows/auto-label-issues.yml, under jobs: auto-label:, insert:

    permissions:
      contents: read
      issues: write

indented to align with runs-on. No imports or additional methods are needed; we are only adjusting YAML configuration for GitHub Actions.

Suggested changeset 1
.github/workflows/auto-label-issues.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/auto-label-issues.yml b/.github/workflows/auto-label-issues.yml
--- a/.github/workflows/auto-label-issues.yml
+++ b/.github/workflows/auto-label-issues.yml
@@ -6,6 +6,9 @@
 jobs:
   auto-label:
     runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      issues: write
     steps:
       - name: Label based on title
         uses: actions/github-script@v7
EOF
@@ -6,6 +6,9 @@
jobs:
auto-label:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- name: Label based on title
uses: actions/github-script@v7
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +8 to +29
runs-on: ubuntu-latest
if: contains(github.event.issue.body, 'Part of #412') || contains(github.event.issue.body, 'Parent Epic')
steps:
- name: Update epic status
uses: actions/github-script@v7
with:
script: |
// Find parent epic from issue body
const body = context.payload.issue.body;
const epicMatch = body.match(/#(\d+)/);
if (epicMatch) {
const epicNumber = epicMatch[1];

// Comment on epic about sub-task status change
const status = context.payload.action === 'closed' ? 'completed' : 'reopened';
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: epicNumber,
body: `📊 Sub-task #${context.issue.number} has been **${status}**`
});
}

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI 7 days ago

In general, to fix this issue you should add a permissions block either at the root of the workflow (to affect all jobs) or inside the specific job that needs permissions. That block should request only the scopes actually required. This workflow needs to create comments on issues, so it requires issues: write. It doesn’t read or modify repository contents directly, so we can set contents: read as a conservative baseline and avoid broader write permissions.

The best targeted fix without changing behavior is to add a permissions block under the sync-epic-progress job. This keeps the change localized and clearly tied to the job that needs it. Concretely, in .github/workflows/epic-subtask-sync.yml, just below runs-on: ubuntu-latest on line 8, insert:

    permissions:
      contents: read
      issues: write

No imports or additional methods are needed because this is a YAML configuration change only. The rest of the workflow logic using actions/github-script@v7 remains unchanged.

Suggested changeset 1
.github/workflows/epic-subtask-sync.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/epic-subtask-sync.yml b/.github/workflows/epic-subtask-sync.yml
--- a/.github/workflows/epic-subtask-sync.yml
+++ b/.github/workflows/epic-subtask-sync.yml
@@ -6,6 +6,9 @@
 jobs:
   sync-epic-progress:
     runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      issues: write
     if: contains(github.event.issue.body, 'Part of #412') || contains(github.event.issue.body, 'Parent Epic')
     steps:
       - name: Update epic status
EOF
@@ -6,6 +6,9 @@
jobs:
sync-epic-progress:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
if: contains(github.event.issue.body, 'Part of #412') || contains(github.event.issue.body, 'Parent Epic')
steps:
- name: Update epic status
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +11 to +34
runs-on: ubuntu-latest
steps:
- name: Move to In Progress on assignment
if: github.event_name == 'issues' && github.event.action == 'assigned'
uses: actions/github-script@v7
with:
script: |
const projectId = 'PVT_kwDOAnuWxs4BRcst';
const statusFieldId = 'PVTSSF_lADOAnuWxs4BRcstzg_RgWc';
const inProgressOptionId = '47fc9ee4';

// Update issue status in project
await github.graphql(`
mutation {
updateProjectV2ItemFieldValue: updateProjectV2ItemFieldValue(
input: {
projectId: "${projectId}"
itemId: "${{ github.event.issue.node_id }}"
fieldId: "${statusFieldId}"
value: { singleSelectOptionId: "${inProgressOptionId}" }
}
) { clientMutationId }
}
`);

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI 7 days ago

In general, the fix is to add an explicit permissions block to the workflow (either at the root, under name:/on:, or specifically under the update-project-status job) that grants only the minimum required access to GITHUB_TOKEN. This documents the intended privileges and ensures they remain constrained even if repository or organization defaults change.

For this specific workflow, the job uses github.graphql to run a mutation that updates a Project V2 item field. That operation requires permission to write to the project resource (projects: write), but does not require write access to repository contents. The safest, least-privilege change that preserves existing behavior is to add a job-level permissions block under update-project-status: specifying projects: write and, if no other steps require repository contents, leaving out contents: write (GitHub will implicitly allow read access where needed; if you want to be explicit, you can also add contents: read). No imports or additional methods are needed; this is purely a YAML configuration change.

Concretely, update .github/workflows/project-auto-progress.yml so that under jobs: -> update-project-status: and before runs-on: ubuntu-latest, you insert:

    permissions:
      projects: write

This ensures that the GITHUB_TOKEN used by actions/github-script@v7 has project write access while avoiding overly broad repository-wide write permissions.


Suggested changeset 1
.github/workflows/project-auto-progress.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/project-auto-progress.yml b/.github/workflows/project-auto-progress.yml
--- a/.github/workflows/project-auto-progress.yml
+++ b/.github/workflows/project-auto-progress.yml
@@ -8,6 +8,8 @@
 
 jobs:
   update-project-status:
+    permissions:
+      projects: write
     runs-on: ubuntu-latest
     steps:
       - name: Move to In Progress on assignment
EOF
@@ -8,6 +8,8 @@

jobs:
update-project-status:
permissions:
projects: write
runs-on: ubuntu-latest
steps:
- name: Move to In Progress on assignment
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +9 to +20
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue has been inactive for 30 days. Please update or it will be closed in 7 days.'
stale-pr-message: 'This PR has been inactive for 30 days. Please update or it will be closed in 7 days.'
close-issue-message: 'Closing due to inactivity. Reopen if still needed.'
days-before-stale: 30
days-before-close: 7
exempt-issue-labels: 'epic,in-progress,priority-high'
exempt-pr-labels: 'in-review'

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: write, issues: write, pull-requests: write}

Copilot Autofix

AI 7 days ago

To fix the problem, explicitly declare the minimal required permissions for the GITHUB_TOKEN in this workflow. Since actions/stale needs to read repository contents (to load issues/PRs) and write to issues and pull requests (to label, comment, and close them), we should set contents: read, issues: write, and pull-requests: write. These can be set at the workflow root so they apply to all jobs, which keeps the configuration simple and avoids changing functionality.

Concretely, in .github/workflows/stale-issue-management.yml, add a permissions: block near the top of the file, at the same indentation level as on: and jobs:. No imports or additional methods are needed; this is pure workflow configuration. The existing logic and steps remain unchanged, but the token will now have clearly defined, least‑privilege access.

Suggested changeset 1
.github/workflows/stale-issue-management.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/stale-issue-management.yml b/.github/workflows/stale-issue-management.yml
--- a/.github/workflows/stale-issue-management.yml
+++ b/.github/workflows/stale-issue-management.yml
@@ -3,6 +3,10 @@
   schedule:
     - cron: '0 0 * * 1'  # Weekly on Monday
   workflow_dispatch:
+permissions:
+  contents: read
+  issues: write
+  pull-requests: write
 
 jobs:
   stale:
EOF
@@ -3,6 +3,10 @@
schedule:
- cron: '0 0 * * 1' # Weekly on Monday
workflow_dispatch:
permissions:
contents: read
issues: write
pull-requests: write

jobs:
stale:
Copilot is powered by AI and may make mistakes. Always verify output.
@qltysh
Copy link

qltysh bot commented Mar 11, 2026

❌ 9 blocking issues (9 total)

Tool Category Rule Count
zizmor Vulnerability overly broad permissions 4
prettier Style Incorrect formatting, autoformat by running qlty fmt. 3
actionlint Lint got unexpected EOF while lexing end of string literal, expecting ''' 1
zizmor Vulnerability code injection via template expansion 1

@qltysh one-click actions:

  • Auto-fix formatting (qlty fmt && git push)

@@ -0,0 +1,34 @@
name: Auto-Label Issues
Copy link

Choose a reason for hiding this comment

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

Incorrect formatting, autoformat by running qlty fmt. [prettier:fmt]

issue_number: context.issue.number,
labels: labels
});
}
Copy link

Choose a reason for hiding this comment

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

overly broad permissions [zizmor:zizmor/excessive-permissions]

jobs:
sync-epic-progress:
runs-on: ubuntu-latest
if: contains(github.event.issue.body, 'Part of #412') || contains(github.event.issue.body, 'Parent Epic')
Copy link

Choose a reason for hiding this comment

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

got unexpected EOF while lexing end of string literal, expecting ''' [actionlint:expression]

issue_number: epicNumber,
body: `📊 Sub-task #${context.issue.number} has been **${status}**`
});
}
Copy link

Choose a reason for hiding this comment

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

overly broad permissions [zizmor:zizmor/excessive-permissions]

@@ -0,0 +1,34 @@
name: Project Auto-Progress
Copy link

Choose a reason for hiding this comment

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

Incorrect formatting, autoformat by running qlty fmt. [prettier:fmt]

updateProjectV2ItemFieldValue: updateProjectV2ItemFieldValue(
input: {
projectId: "${projectId}"
itemId: "${{ github.event.issue.node_id }}"
Copy link

Choose a reason for hiding this comment

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

code injection via template expansion [zizmor:zizmor/template-injection]

}
) { clientMutationId }
}
`);
Copy link

Choose a reason for hiding this comment

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

overly broad permissions [zizmor:zizmor/excessive-permissions]

@@ -0,0 +1,20 @@
name: Stale Issue Management
Copy link

Choose a reason for hiding this comment

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

Incorrect formatting, autoformat by running qlty fmt. [prettier:fmt]

days-before-stale: 30
days-before-close: 7
exempt-issue-labels: 'epic,in-progress,priority-high'
exempt-pr-labels: 'in-review'
Copy link

Choose a reason for hiding this comment

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

overly broad permissions [zizmor:zizmor/excessive-permissions]

@greptile-apps
Copy link

greptile-apps bot commented Mar 11, 2026

Greptile Summary

This PR introduces four GitHub Actions automation workflows for the Sobers v2 project: auto-labelling issues by title keyword, moving issues to "In Progress" in Project #4 on assignment, syncing parent epic comments when sub-tasks change state, and weekly stale issue/PR cleanup. The workflows integrate well with the existing project structure, but two logic bugs in epic-subtask-sync.yml need to be addressed before merging.

Key findings:

  • epic-subtask-sync.yml (critical): context.payload.issue.body can be null for issues closed without a description — calling .match() on null throws a TypeError and crashes the step. Additionally, the regex /#(\d+)/ matches the first #number in the body, which may be a Fixes #X or Closes #Y reference rather than the intended parent epic, causing the status comment to be posted on the wrong issue.
  • auto-label-issues.yml & epic-subtask-sync.yml: API calls (addLabels, createComment) are not awaited, so errors are silently swallowed and the step always appears to succeed.
  • project-auto-progress.yml: The pull_request: types: [opened] and issues: types: [reopened] triggers are declared but no step handles them, causing unnecessary runner invocations with no effect. The ${{ github.event.issue.node_id }} interpolation directly inside the script: block is an injection anti-pattern; using an env: variable is the recommended approach.
  • stale-issue-management.yml: Clean implementation with no issues found.

Confidence Score: 2/5

  • Not safe to merge — two runtime-crashing logic bugs in epic-subtask-sync.yml must be fixed first.
  • The null-body crash and incorrect regex match in epic-subtask-sync.yml will cause real runtime failures in production (any issue closed without a body, or any issue body that contains a Fixes #X reference before the parent epic line). These are not theoretical — they represent reliably reproducible failure modes that would leave the epic sync broken silently or post comments on wrong issues.
  • epic-subtask-sync.yml requires immediate attention for the two logic bugs; project-auto-progress.yml needs the unused triggers removed and the injection pattern addressed.

Important Files Changed

Filename Overview
.github/workflows/auto-label-issues.yml Auto-labels new issues by title keyword; missing await on the addLabels API call silently swallows errors.
.github/workflows/epic-subtask-sync.yml Syncs parent epic on sub-task close/reopen; two logic bugs: null body crash and regex matching the wrong issue number if the body has other references.
.github/workflows/project-auto-progress.yml Moves issues to "In Progress" in Project #4 via GraphQL; unused pull_request and reopened triggers waste CI minutes, and ${{ }} interpolation inside the script block is an injection anti-pattern.
.github/workflows/stale-issue-management.yml Weekly stale issue/PR cleanup with reasonable defaults (30-day stale, 7-day close) and appropriate label exemptions; no issues found.

Sequence Diagram

sequenceDiagram
    participant GH as GitHub Event
    participant AL as auto-label-issues
    participant PP as project-auto-progress
    participant ES as epic-subtask-sync
    participant SM as stale-issue-management

    GH->>AL: issue opened
    AL->>AL: match title keywords
    AL-->>GH: addLabels (bug/enhancement/docs/testing)

    GH->>PP: issue assigned
    PP->>GH: GraphQL mutation → Project #4 status = "In Progress"

    GH->>ES: issue closed / reopened
    ES->>ES: parse body for parent epic #
    ES-->>GH: createComment on parent epic

    Note over SM: runs weekly (Monday 00:00 UTC)
    SM->>GH: mark issues stale after 30d
    SM->>GH: close stale issues after 7d
    Note over SM: exempt: epic, in-progress, priority-high
Loading

Last reviewed commit: 998efab

// Find parent epic from issue body
const body = context.payload.issue.body;
const epicMatch = body.match(/#(\d+)/);
if (epicMatch) {
Copy link

Choose a reason for hiding this comment

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

Regex matches first #number, not the parent epic reference

body.match(/#(\d+)/) matches the first #number occurrence anywhere in the issue body. If the body contains other issue references before the "Part of #…" line (e.g., Fixes #100 or Closes #200), the workflow will post the status comment on that wrong issue rather than the actual parent epic.

Consider anchoring the match to a specific prefix pattern:

Suggested change
if (epicMatch) {
const epicMatch = body.match(/[Pp]art of #(\d+)/);

This ensures only the intended parent-epic reference is captured.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/epic-subtask-sync.yml
Line: 18

Comment:
**Regex matches first `#number`, not the parent epic reference**

`body.match(/#(\d+)/)` matches the **first** `#number` occurrence anywhere in the issue body. If the body contains other issue references before the "Part of #…" line (e.g., `Fixes #100` or `Closes #200`), the workflow will post the status comment on that wrong issue rather than the actual parent epic.

Consider anchoring the match to a specific prefix pattern:

```suggestion
            const epicMatch = body.match(/[Pp]art of #(\d+)/);
```

This ensures only the intended parent-epic reference is captured.

How can I resolve this? If you propose a fix, please make it concise.

uses: actions/github-script@v7
with:
script: |
// Find parent epic from issue body
Copy link

Choose a reason for hiding this comment

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

Null body causes runtime crash

context.payload.issue.body can be null when an issue is closed without a description. Calling .match() on null will throw TypeError: Cannot read properties of null, causing the entire workflow step to fail.

Add a null-guard before matching:

Suggested change
// Find parent epic from issue body
const body = context.payload.issue.body || '';
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/epic-subtask-sync.yml
Line: 15

Comment:
**Null body causes runtime crash**

`context.payload.issue.body` can be `null` when an issue is closed without a description. Calling `.match()` on `null` will throw `TypeError: Cannot read properties of null`, causing the entire workflow step to fail.

Add a null-guard before matching:

```suggestion
            const body = context.payload.issue.body || '';
```

How can I resolve this? If you propose a fix, please make it concise.

const epicNumber = epicMatch[1];

// Comment on epic about sub-task status change
const status = context.payload.action === 'closed' ? 'completed' : 'reopened';
Copy link

Choose a reason for hiding this comment

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

Missing await on API call

github.rest.issues.createComment(...) returns a Promise but it is not awaited. If the API call fails (e.g., rate-limit, network error), the error is silently swallowed and the workflow step exits successfully despite the failure.

Suggested change
const status = context.payload.action === 'closed' ? 'completed' : 'reopened';
await github.rest.issues.createComment({
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/epic-subtask-sync.yml
Line: 22

Comment:
**Missing `await` on API call**

`github.rest.issues.createComment(...)` returns a Promise but it is not awaited. If the API call fails (e.g., rate-limit, network error), the error is silently swallowed and the workflow step exits successfully despite the failure.

```suggestion
              await github.rest.issues.createComment({
```

How can I resolve this? If you propose a fix, please make it concise.


if (labels.length > 0) {
github.rest.issues.addLabels({
owner: context.repo.owner,
Copy link

Choose a reason for hiding this comment

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

Missing await on API call

github.rest.issues.addLabels(...) is not awaited. Errors (e.g., non-existent label, permission failure) will be silently swallowed and the step will appear to succeed.

Suggested change
owner: context.repo.owner,
await github.rest.issues.addLabels({
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/auto-label-issues.yml
Line: 29

Comment:
**Missing `await` on API call**

`github.rest.issues.addLabels(...)` is not awaited. Errors (e.g., non-existent label, permission failure) will be silently swallowed and the step will appear to succeed.

```suggestion
              await github.rest.issues.addLabels({
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +2 to +8
on:
issues:
types: [assigned, reopened]
pull_request:
types: [opened]
branches: [main, develop]

Copy link

Choose a reason for hiding this comment

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

pull_request: opened trigger fires but no step handles it

The workflow declares a pull_request: types: [opened] trigger, but the only step inside the job is guarded by if: github.event_name == 'issues' && github.event.action == 'assigned'. When a PR is opened, the job is queued, consumes a runner, and then immediately exits because the condition is false — no work is done.

Similarly, the issues: reopened event is declared but there is no step that handles it either.

Either add the missing steps for these events or remove the unused triggers:

Suggested change
on:
issues:
types: [assigned, reopened]
pull_request:
types: [opened]
branches: [main, develop]
on:
issues:
types: [assigned]
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/project-auto-progress.yml
Line: 2-8

Comment:
**`pull_request: opened` trigger fires but no step handles it**

The workflow declares a `pull_request: types: [opened]` trigger, but the only step inside the job is guarded by `if: github.event_name == 'issues' && github.event.action == 'assigned'`. When a PR is opened, the job is queued, consumes a runner, and then immediately exits because the condition is false — no work is done.

Similarly, the `issues: reopened` event is declared but there is no step that handles it either.

Either add the missing steps for these events or remove the unused triggers:

```suggestion
on:
  issues:
    types: [assigned]
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +16 to +34
with:
script: |
const projectId = 'PVT_kwDOAnuWxs4BRcst';
const statusFieldId = 'PVTSSF_lADOAnuWxs4BRcstzg_RgWc';
const inProgressOptionId = '47fc9ee4';

// Update issue status in project
await github.graphql(`
mutation {
updateProjectV2ItemFieldValue: updateProjectV2ItemFieldValue(
input: {
projectId: "${projectId}"
itemId: "${{ github.event.issue.node_id }}"
fieldId: "${statusFieldId}"
value: { singleSelectOptionId: "${inProgressOptionId}" }
}
) { clientMutationId }
}
`);
Copy link

Choose a reason for hiding this comment

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

Script injection via ${{ }} expression inside script:

Interpolating ${{ github.event.issue.node_id }} directly into the GraphQL mutation string is a script-injection anti-pattern. Although GitHub-generated node IDs are safe today, the GitHub Actions security hardening guide explicitly recommends passing context values through environment variables to avoid any injection risk.

Suggested change
with:
script: |
const projectId = 'PVT_kwDOAnuWxs4BRcst';
const statusFieldId = 'PVTSSF_lADOAnuWxs4BRcstzg_RgWc';
const inProgressOptionId = '47fc9ee4';
// Update issue status in project
await github.graphql(`
mutation {
updateProjectV2ItemFieldValue: updateProjectV2ItemFieldValue(
input: {
projectId: "${projectId}"
itemId: "${{ github.event.issue.node_id }}"
fieldId: "${statusFieldId}"
value: { singleSelectOptionId: "${inProgressOptionId}" }
}
) { clientMutationId }
}
`);
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const projectId = 'PVT_kwDOAnuWxs4BRcst';
const statusFieldId = 'PVTSSF_lADOAnuWxs4BRcstzg_RgWc';
const inProgressOptionId = '47fc9ee4';
const itemId = process.env.ITEM_ID;
await github.graphql(`
mutation {
updateProjectV2ItemFieldValue: updateProjectV2ItemFieldValue(
input: {
projectId: "${projectId}"
itemId: "${itemId}"
fieldId: "${statusFieldId}"
value: { singleSelectOptionId: "${inProgressOptionId}" }
}
) { clientMutationId }
}
`);
env:
ITEM_ID: ${{ github.event.issue.node_id }}
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/project-auto-progress.yml
Line: 16-34

Comment:
**Script injection via `${{ }}` expression inside `script:`**

Interpolating `${{ github.event.issue.node_id }}` directly into the GraphQL mutation string is a script-injection anti-pattern. Although GitHub-generated node IDs are safe today, the GitHub Actions security hardening guide explicitly recommends passing context values through environment variables to avoid any injection risk.

```suggestion
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const projectId = 'PVT_kwDOAnuWxs4BRcst';
            const statusFieldId = 'PVTSSF_lADOAnuWxs4BRcstzg_RgWc';
            const inProgressOptionId = '47fc9ee4';
            const itemId = process.env.ITEM_ID;
            
            await github.graphql(`
              mutation {
                updateProjectV2ItemFieldValue: updateProjectV2ItemFieldValue(
                  input: {
                    projectId: "${projectId}"
                    itemId: "${itemId}"
                    fieldId: "${statusFieldId}"
                    value: { singleSelectOptionId: "${inProgressOptionId}" }
                  }
                ) { clientMutationId }
              }
            `);
        env:
          ITEM_ID: ${{ github.event.issue.node_id }}
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/auto-label-issues.yml:
- Around line 17-25: The current title checks using title.includes(...) cause
false positives; update the conditional matching in the auto-labeling logic (the
checks referencing title and the includes(...) calls) to use whole-word,
case-insensitive matching (e.g., test with a word-boundary regex like /\bfix\b/i
or split the title into tokens) for each label category so substrings like
"contest", "fixed width", or "additional" do not trigger the wrong label;
replace each title.includes(...) condition with a proper word-boundary regex
test or token lookup for 'bug'/'fix'/'crash', 'feat'/'add'/'implement',
'docs'/'documentation', and 'test'/'testing'.
- Around line 6-34: Add an explicit permissions block granting write access to
issues and await the asynchronous GitHub API call: update the workflow job
"auto-label" (the step using actions/github-script@v7) to include permissions: {
issues: write } at the job or workflow level, and change the call to
github.rest.issues.addLabels(...) to await github.rest.issues.addLabels(...) so
the labels are applied before the step completes.

In @.github/workflows/epic-subtask-sync.yml:
- Around line 14-28: The script calls context.payload.issue.body.match(...)
without guarding against a null body and calls the async GitHub API
github.rest.issues.createComment(...) without awaiting it; fix by first checking
that context.payload.issue && context.payload.issue.body is non-null (or coerce
to empty string) before calling .match, only proceed if epicMatch is truthy, and
add await before github.rest.issues.createComment so the Promise is handled;
also add a workflow permissions block with "permissions: issues: write" to allow
creating comments.
- Line 9: The conditional expression using contains(github.event.issue.body,
'Part of `#412`') is failing due to the unescaped '#' being parsed as a YAML
comment; update the if condition to quote and escape the hash (e.g., use double
quotes and escape the pound sign) and add a null-safe fallback for issue.body
(use contains(github.event.issue.body || '', "Part of \#412") or similar) so
actionlint/YAML parsing succeeds; locate the contains(...) expression in the if
line and apply the quoted/escaped string and the || '' fallback, and optionally
generalize the pattern instead of hardcoding "#412".

In @.github/workflows/project-auto-progress.yml:
- Around line 5-7: The workflow defines a pull_request trigger but no job
handles PR events, so either remove the "pull_request:" trigger block or add a
job/step that runs when github.event_name == 'pull_request' (or update the
existing step condition from github.event_name == 'issues' to include
pull_request); locate the trigger named "pull_request" and the step condition
using "github.event_name == 'issues'" and either delete the pull_request trigger
or change the condition to handle 'pull_request' (or use a combined conditional
like github.event_name == 'issues' || github.event_name == 'pull_request') so PR
events actually execute a step.
- Around line 17-34: The mutation is using github.event.issue.node_id as itemId
which is incorrect; first query the ProjectV2Item ID for the issue (use the
issue's projectItems connection or call addProjectV2ItemById) and then pass that
PVTI_... ID into updateProjectV2ItemFieldValue (refer to
updateProjectV2ItemFieldValue, github.event.issue.node_id, projectItems,
addProjectV2ItemById to locate the code). Also replace the default GITHUB_TOKEN
or ensure you use a token with Projects V2 write scope (use a PAT or a GitHub
App installation token and store it in secrets) because the permissions:
projects: write block won’t grant Projects V2 mutation rights.

In @.github/workflows/stale-issue-management.yml:
- Around line 7-20: The workflow's stale job using actions/stale@v9 needs
explicit least-privilege permissions and should stop passing the deprecated
repo-token; add a top-level permissions block granting only issues: write and
pull-requests: write (or issues: write and checks: write if PR write isn’t
supported) and remove the repo-token: ${{ secrets.GITHUB_TOKEN }} line from the
stale step's with: block so actions/stale uses the default GITHUB_TOKEN with the
specified limited permissions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e4336414-7e3c-4c07-b363-849ff6976d23

📥 Commits

Reviewing files that changed from the base of the PR and between b0b71f8 and 998efab.

📒 Files selected for processing (4)
  • .github/workflows/auto-label-issues.yml
  • .github/workflows/epic-subtask-sync.yml
  • .github/workflows/project-auto-progress.yml
  • .github/workflows/stale-issue-management.yml

Comment on lines +6 to +34
jobs:
auto-label:
runs-on: ubuntu-latest
steps:
- name: Label based on title
uses: actions/github-script@v7
with:
script: |
const title = context.payload.issue.title.toLowerCase();
const labels = [];

if (title.includes('bug') || title.includes('fix') || title.includes('crash')) {
labels.push('bug');
} else if (title.includes('feat') || title.includes('add') || title.includes('implement')) {
labels.push('enhancement');
} else if (title.includes('docs') || title.includes('documentation')) {
labels.push('documentation');
} else if (title.includes('test') || title.includes('testing')) {
labels.push('testing');
}

if (labels.length > 0) {
github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: labels
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add permissions block and await the async API call.

The addLabels call is async and should be awaited to ensure labels are applied before the workflow completes. Also add explicit permissions.

🐛 Proposed fix
 on:
   issues:
     types: [opened]

+permissions:
+  issues: write
+
 jobs:
   auto-label:
     runs-on: ubuntu-latest
     steps:
       - name: Label based on title
         uses: actions/github-script@v7
         with:
           script: |
             const title = context.payload.issue.title.toLowerCase();
             const labels = [];
             
             if (title.includes('bug') || title.includes('fix') || title.includes('crash')) {
               labels.push('bug');
             } else if (title.includes('feat') || title.includes('add') || title.includes('implement')) {
               labels.push('enhancement');
             } else if (title.includes('docs') || title.includes('documentation')) {
               labels.push('documentation');
             } else if (title.includes('test') || title.includes('testing')) {
               labels.push('testing');
             }
             
             if (labels.length > 0) {
-              github.rest.issues.addLabels({
+              await github.rest.issues.addLabels({
                 owner: context.repo.owner,
                 repo: context.repo.repo,
                 issue_number: context.issue.number,
                 labels: labels
               });
             }
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 8-34: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{}}

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/auto-label-issues.yml around lines 6 - 34, Add an explicit
permissions block granting write access to issues and await the asynchronous
GitHub API call: update the workflow job "auto-label" (the step using
actions/github-script@v7) to include permissions: { issues: write } at the job
or workflow level, and change the call to github.rest.issues.addLabels(...) to
await github.rest.issues.addLabels(...) so the labels are applied before the
step completes.

Comment on lines +17 to +25
if (title.includes('bug') || title.includes('fix') || title.includes('crash')) {
labels.push('bug');
} else if (title.includes('feat') || title.includes('add') || title.includes('implement')) {
labels.push('enhancement');
} else if (title.includes('docs') || title.includes('documentation')) {
labels.push('documentation');
} else if (title.includes('test') || title.includes('testing')) {
labels.push('testing');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider false positive risks with substring matching.

Using includes() may produce unintended matches:

  • "contest" matches "test" → gets testing label
  • "fixed width" matches "fix" → gets bug label
  • "additional" matches "add" → gets enhancement label
💡 Optional: Use word boundary regex for more precise matching
-            if (title.includes('bug') || title.includes('fix') || title.includes('crash')) {
+            if (/\b(bug|fix|crash)\b/.test(title)) {
               labels.push('bug');
-            } else if (title.includes('feat') || title.includes('add') || title.includes('implement')) {
+            } else if (/\b(feat|feature|add|implement)\b/.test(title)) {
               labels.push('enhancement');
-            } else if (title.includes('docs') || title.includes('documentation')) {
+            } else if (/\b(docs|documentation)\b/.test(title)) {
               labels.push('documentation');
-            } else if (title.includes('test') || title.includes('testing')) {
+            } else if (/\b(test|testing)\b/.test(title)) {
               labels.push('testing');
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/auto-label-issues.yml around lines 17 - 25, The current
title checks using title.includes(...) cause false positives; update the
conditional matching in the auto-labeling logic (the checks referencing title
and the includes(...) calls) to use whole-word, case-insensitive matching (e.g.,
test with a word-boundary regex like /\bfix\b/i or split the title into tokens)
for each label category so substrings like "contest", "fixed width", or
"additional" do not trigger the wrong label; replace each title.includes(...)
condition with a proper word-boundary regex test or token lookup for
'bug'/'fix'/'crash', 'feat'/'add'/'implement', 'docs'/'documentation', and
'test'/'testing'.

jobs:
sync-epic-progress:
runs-on: ubuntu-latest
if: contains(github.event.issue.body, 'Part of #412') || contains(github.event.issue.body, 'Parent Epic')
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

YAML syntax error: # character is interpreted as comment start.

The actionlint error confirms that the # in 'Part of #412' causes a YAML parsing issue. The string needs proper quoting to prevent # from being treated as a comment delimiter.

🐛 Proposed fix using double quotes and escaping
-    if: contains(github.event.issue.body, 'Part of `#412`') || contains(github.event.issue.body, 'Parent Epic')
+    if: contains(github.event.issue.body || '', 'Part of #') || contains(github.event.issue.body || '', 'Parent Epic')

Note: Also added || '' fallback since issue.body can be null, which would cause contains() to fail.

Consider making the pattern more generic rather than hardcoding #412.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if: contains(github.event.issue.body, 'Part of #412') || contains(github.event.issue.body, 'Parent Epic')
if: contains(github.event.issue.body || '', 'Part of #') || contains(github.event.issue.body || '', 'Parent Epic')
🧰 Tools
🪛 actionlint (1.7.11)

[error] 9-9: got unexpected EOF while lexing end of string literal, expecting '''

(expression)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/epic-subtask-sync.yml at line 9, The conditional
expression using contains(github.event.issue.body, 'Part of `#412`') is failing
due to the unescaped '#' being parsed as a YAML comment; update the if condition
to quote and escape the hash (e.g., use double quotes and escape the pound sign)
and add a null-safe fallback for issue.body (use
contains(github.event.issue.body || '', "Part of \#412") or similar) so
actionlint/YAML parsing succeeds; locate the contains(...) expression in the if
line and apply the quoted/escaped string and the || '' fallback, and optionally
generalize the pattern instead of hardcoding "#412".

Comment on lines +14 to +28
script: |
// Find parent epic from issue body
const body = context.payload.issue.body;
const epicMatch = body.match(/#(\d+)/);
if (epicMatch) {
const epicNumber = epicMatch[1];

// Comment on epic about sub-task status change
const status = context.payload.action === 'closed' ? 'completed' : 'reopened';
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: epicNumber,
body: `📊 Sub-task #${context.issue.number} has been **${status}**`
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing await on async API call and potential null reference.

  1. github.rest.issues.createComment() returns a Promise that should be awaited.
  2. context.payload.issue.body can be null if no body was provided, causing .match() to throw.
🐛 Proposed fix
           script: |
             // Find parent epic from issue body
-            const body = context.payload.issue.body;
+            const body = context.payload.issue.body || '';
             const epicMatch = body.match(/#(\d+)/);
             if (epicMatch) {
               const epicNumber = epicMatch[1];
               
               // Comment on epic about sub-task status change
               const status = context.payload.action === 'closed' ? 'completed' : 'reopened';
-              github.rest.issues.createComment({
+              await github.rest.issues.createComment({
                 owner: context.repo.owner,
                 repo: context.repo.repo,
                 issue_number: epicNumber,
                 body: `📊 Sub-task #${context.issue.number} has been **${status}**`
               });
             }

Also add permissions block:

permissions:
  issues: write
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/epic-subtask-sync.yml around lines 14 - 28, The script
calls context.payload.issue.body.match(...) without guarding against a null body
and calls the async GitHub API github.rest.issues.createComment(...) without
awaiting it; fix by first checking that context.payload.issue &&
context.payload.issue.body is non-null (or coerce to empty string) before
calling .match, only proceed if epicMatch is truthy, and add await before
github.rest.issues.createComment so the Promise is handled; also add a workflow
permissions block with "permissions: issues: write" to allow creating comments.

Comment on lines +5 to +7
pull_request:
types: [opened]
branches: [main, develop]
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

PR trigger defined but no corresponding step handles it.

The workflow triggers on pull_request events (lines 5-7), but the only step has a condition github.event_name == 'issues' (line 14), so PR events will trigger the workflow but execute nothing.

Either add a step to handle PR events or remove the unused trigger.

🛠️ Option A: Remove unused PR trigger
 on:
   issues:
     types: [assigned, reopened]
-  pull_request:
-    types: [opened]
-    branches: [main, develop]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pull_request:
types: [opened]
branches: [main, develop]
on:
issues:
types: [assigned, reopened]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/project-auto-progress.yml around lines 5 - 7, The workflow
defines a pull_request trigger but no job handles PR events, so either remove
the "pull_request:" trigger block or add a job/step that runs when
github.event_name == 'pull_request' (or update the existing step condition from
github.event_name == 'issues' to include pull_request); locate the trigger named
"pull_request" and the step condition using "github.event_name == 'issues'" and
either delete the pull_request trigger or change the condition to handle
'pull_request' (or use a combined conditional like github.event_name == 'issues'
|| github.event_name == 'pull_request') so PR events actually execute a step.

Comment on lines +17 to +34
script: |
const projectId = 'PVT_kwDOAnuWxs4BRcst';
const statusFieldId = 'PVTSSF_lADOAnuWxs4BRcstzg_RgWc';
const inProgressOptionId = '47fc9ee4';

// Update issue status in project
await github.graphql(`
mutation {
updateProjectV2ItemFieldValue: updateProjectV2ItemFieldValue(
input: {
projectId: "${projectId}"
itemId: "${{ github.event.issue.node_id }}"
fieldId: "${statusFieldId}"
value: { singleSelectOptionId: "${inProgressOptionId}" }
}
) { clientMutationId }
}
`);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

GitHub Actions permissions required for Project V2 GraphQL mutations

💡 Result:

For Projects (Project V2) GraphQL mutations (e.g., addProjectV2ItemById, updateProjectV2ItemFieldValue) you can’t enable the needed access just by adding a permissions: block for GITHUB_TOKEN—those workflow permissions (like repository-projects) are not the Projects V2 auth scope and historically map to “projects (classic)” style permissions. [3], [4]

What you need instead

  • Read-only Projects V2 queries: a token with read:project scope. [1]
  • Projects V2 queries + mutations (write): a token with project scope. [1], [2]

In GitHub Actions, that means you typically must provide a PAT (classic PAT with project, or a fine-grained PAT that includes Projects: Read and write) or use a GitHub App installation token that has Projects (organization/user) write permission, because the default GITHUB_TOKEN does not grant the project scope required for Projects V2 mutations. [1], [2]

Practical workflow shape

  • Store the PAT (or App token) in a secret (e.g. PROJECT_TOKEN)
  • Use it as GH_TOKEN / GITHUB_TOKEN for your GraphQL call step (Octokit/gh api graphql/curl). [1], [2]

Sources: GitHub docs for Projects API scopes (read:project vs project) [1], GitHub blog noting the project scope requirement and that Actions must supply such a token separately [2], and GitHub Actions token-permissions material showing repository-projects is a different permission surface (not the Projects V2 project scope). [3], [4]

References:
[1] https://docs.github.com/enterprise-cloud%40latest/issues/planning-and-tracking-with-projects/automating-your-project/using-the-api-to-manage-projects
[2] https://github.blog/developer-skills/github/github-cli-project-command-is-now-generally-available/
[3] https://github.blog/changelog/2021-04-20-github-actions-control-permissions-for-github_token/
[4] https://github.com/orgs/community/discussions/54538


🌐 Web query:

GitHub GraphQL updateProjectV2ItemFieldValue itemId parameter issue node_id

💡 Result:

updateProjectV2ItemFieldValue’s itemId is not the Issue’s node_id. It must be the ProjectV2Item node ID (typically starts with PVTI_...). (docs.github.com)

How to get the right itemId from an Issue node_id:

  1. Add the Issue to the Project (or “add” it again) and read the returned Project item id:
mutation($projectId:ID!, $contentId:ID!) {
  addProjectV2ItemById(input:{ projectId:$projectId, contentId:$contentId }) {
    item { id }
  }
}
  • contentId = the Issue node_id
  • Response item.id = the itemId you must use
  • If the Issue is already in the Project, GitHub returns the existing item id. (docs.github.com)
  1. Then update the field value using that itemId:
mutation($projectId:ID!, $itemId:ID!, $fieldId:ID!) {
  updateProjectV2ItemFieldValue(input:{
    projectId:$projectId, itemId:$itemId, fieldId:$fieldId, value:{ text:"Updated text" }
  }) {
    projectV2Item { id }
  }
}

You can’t add and update the item in the same call. (docs.github.com)

Citations:


Critical: Using issue node_id instead of project item ID will cause mutation to fail.

The GraphQL mutation uses github.event.issue.node_id as the itemId, but this is the issue's node ID, not the project item ID. The updateProjectV2ItemFieldValue mutation requires a ProjectV2Item ID (typically starts with PVTI_...).

You must first query for the project item ID associated with this issue using addProjectV2ItemById or by querying the issue's projectItems connection.

🐛 Proposed fix: Query for project item ID first
           script: |
             const projectId = 'PVT_kwDOAnuWxs4BRcst';
             const statusFieldId = 'PVTSSF_lADOAnuWxs4BRcstzg_RgWc';
             const inProgressOptionId = '47fc9ee4';
+            const issueNodeId = '${{ github.event.issue.node_id }}';
             
-            // Update issue status in project
+            // First, find or create the project item for this issue
+            const addResult = await github.graphql(`
+              mutation($projectId: ID!, $contentId: ID!) {
+                addProjectV2ItemById(input: {
+                  projectId: $projectId
+                  contentId: $contentId
+                }) {
+                  item { id }
+                }
+              }
+            `, { projectId: projectId, contentId: issueNodeId });
+            
+            const itemId = addResult.addProjectV2ItemById.item.id;
+            
+            // Update issue status in project
             await github.graphql(`
               mutation {
                 updateProjectV2ItemFieldValue: updateProjectV2ItemFieldValue(
                   input: {
                     projectId: "${projectId}"
-                    itemId: "${{ github.event.issue.node_id }}"
+                    itemId: "${itemId}"
                     fieldId: "${statusFieldId}"
                     value: { singleSelectOptionId: "${inProgressOptionId}" }
                   }
                 ) { clientMutationId }
               }
             `);

Additionally, the workflow requires a token with Projects V2 write access, but the default GITHUB_TOKEN does not have the project scope required for Projects V2 mutations. You must use a Personal Access Token (PAT with project scope) or a GitHub App installation token with Projects write permission instead. The permissions: projects: write block is for classic projects and will not enable Projects V2 mutations.

🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 11-34: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{}}

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/project-auto-progress.yml around lines 17 - 34, The
mutation is using github.event.issue.node_id as itemId which is incorrect; first
query the ProjectV2Item ID for the issue (use the issue's projectItems
connection or call addProjectV2ItemById) and then pass that PVTI_... ID into
updateProjectV2ItemFieldValue (refer to updateProjectV2ItemFieldValue,
github.event.issue.node_id, projectItems, addProjectV2ItemById to locate the
code). Also replace the default GITHUB_TOKEN or ensure you use a token with
Projects V2 write scope (use a PAT or a GitHub App installation token and store
it in secrets) because the permissions: projects: write block won’t grant
Projects V2 mutation rights.

Comment on lines +7 to +20
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue has been inactive for 30 days. Please update or it will be closed in 7 days.'
stale-pr-message: 'This PR has been inactive for 30 days. Please update or it will be closed in 7 days.'
close-issue-message: 'Closing due to inactivity. Reopen if still needed.'
days-before-stale: 30
days-before-close: 7
exempt-issue-labels: 'epic,in-progress,priority-high'
exempt-pr-labels: 'in-review'
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add explicit permissions block for security hardening.

The workflow uses actions/stale@v9 which requires write access to issues and pull requests. Adding explicit permissions follows the principle of least privilege and addresses the CodeQL warning.

Also, repo-token is deprecated in newer versions of actions/stale; the action now uses GITHUB_TOKEN by default.

🔒 Proposed fix to add permissions and remove deprecated parameter
 on:
   schedule:
     - cron: '0 0 * * 1'  # Weekly on Monday
   workflow_dispatch:

+permissions:
+  issues: write
+  pull-requests: write
+
 jobs:
   stale:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/stale@v9
         with:
-          repo-token: ${{ secrets.GITHUB_TOKEN }}
           stale-issue-message: 'This issue has been inactive for 30 days. Please update or it will be closed in 7 days.'
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 9-20: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{contents: write, issues: write, pull-requests: write}}

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/stale-issue-management.yml around lines 7 - 20, The
workflow's stale job using actions/stale@v9 needs explicit least-privilege
permissions and should stop passing the deprecated repo-token; add a top-level
permissions block granting only issues: write and pull-requests: write (or
issues: write and checks: write if PR write isn’t supported) and remove the
repo-token: ${{ secrets.GITHUB_TOKEN }} line from the stale step's with: block
so actions/stale uses the default GITHUB_TOKEN with the specified limited
permissions.

@github-project-automation github-project-automation bot moved this from Todo to In Progress in Sobers v2 Mar 11, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds GitHub Actions automation to manage Sobers v2 project workflow hygiene (auto-progress, auto-labeling, epic/subtask notifications, and stale triage) to support issue/PR tracking for Project #4 and the Sobers Buddy epic (#412).

Changes:

  • Add a scheduled stale-management workflow for issues/PRs.
  • Add issue-title-based auto-labeling on issue open.
  • Add workflows to auto-progress items and to notify/sync epic status when subtasks change.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 10 comments.

File Description
.github/workflows/stale-issue-management.yml Scheduled stale triage/closure with label-based exemptions.
.github/workflows/project-auto-progress.yml Attempts to move assigned issues into “In Progress” in Project V2.
.github/workflows/epic-subtask-sync.yml On subtask close/reopen, identifies epic and posts an update comment.
.github/workflows/auto-label-issues.yml Applies labels based on issue title keywords on issue creation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +9 to +19
if: contains(github.event.issue.body, 'Part of #412') || contains(github.event.issue.body, 'Parent Epic')
steps:
- name: Update epic status
uses: actions/github-script@v7
with:
script: |
// Find parent epic from issue body
const body = context.payload.issue.body;
const epicMatch = body.match(/#(\d+)/);
if (epicMatch) {
const epicNumber = epicMatch[1];
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

The parent epic detection is hard-coded to Part of #412 and then uses body.match(/#(\d+)/) (first issue reference in the body). This can easily pick the wrong issue if the body contains other #123 references and makes the workflow non-reusable for future epics. Prefer parsing a specific marker like Part of #(\d+)/Parent Epic: #(\d+) and using the captured epic number.

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +28
const status = context.payload.action === 'closed' ? 'completed' : 'reopened';
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: epicNumber,
body: `📊 Sub-task #${context.issue.number} has been **${status}**`
});
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

The REST call to github.rest.issues.createComment(...) is not awaited/returned. If the request fails, the workflow may still report success or behave inconsistently. Use await (or return) so failures surface and execution waits for the API call to complete.

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +33
if (labels.length > 0) {
github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: labels
});
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

The REST call to github.rest.issues.addLabels(...) is not awaited/returned. If the API call fails (missing label, permission issue, rate limit), the workflow can still succeed silently. Use await (or return) so failures are surfaced and the step waits for completion.

Copilot uses AI. Check for mistakes.

jobs:
stale:
runs-on: ubuntu-latest
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

actions/stale needs write permissions to apply labels/comments and close items. Add an explicit permissions: block (e.g., issues: write and pull-requests: write if PRs are included) so the workflow doesn't depend on repository-wide default token permissions.

Suggested change
runs-on: ubuntu-latest
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +12
jobs:
sync-epic-progress:
runs-on: ubuntu-latest
if: contains(github.event.issue.body, 'Part of #412') || contains(github.event.issue.body, 'Parent Epic')
steps:
- name: Update epic status
uses: actions/github-script@v7
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

This workflow creates comments via the GitHub API; add an explicit permissions: block (at least issues: write) so it works when default GITHUB_TOKEN permissions are read-only/restricted.

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +34

// Update issue status in project
await github.graphql(`
mutation {
updateProjectV2ItemFieldValue: updateProjectV2ItemFieldValue(
input: {
projectId: "${projectId}"
itemId: "${{ github.event.issue.node_id }}"
fieldId: "${statusFieldId}"
value: { singleSelectOptionId: "${inProgressOptionId}" }
}
) { clientMutationId }
}
`);
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

updateProjectV2ItemFieldValue expects a ProjectV2Item ID for itemId, but this uses the issue's node_id (content ID). This mutation will fail unless you first add the issue to the project (e.g., addProjectV2ItemById) or query the project's item by contentId and use that item ID.

Suggested change
// Update issue status in project
await github.graphql(`
mutation {
updateProjectV2ItemFieldValue: updateProjectV2ItemFieldValue(
input: {
projectId: "${projectId}"
itemId: "${{ github.event.issue.node_id }}"
fieldId: "${statusFieldId}"
value: { singleSelectOptionId: "${inProgressOptionId}" }
}
) { clientMutationId }
}
`);
// Get the issue's node ID from the event payload
const issueNodeId = context.payload.issue.node_id;
// Ensure the issue is in the project and get the ProjectV2Item ID
const addItemResult = await github.graphql(
`
mutation ($projectId: ID!, $contentId: ID!) {
addProjectV2ItemById(
input: { projectId: $projectId, contentId: $contentId }
) {
item {
id
}
}
}
`,
{
projectId,
contentId: issueNodeId,
}
);
const itemId = addItemResult.addProjectV2ItemById.item.id;
// Update issue status in project
await github.graphql(
`
mutation (
$projectId: ID!
$itemId: ID!
$fieldId: ID!
$optionId: String!
) {
updateProjectV2ItemFieldValue(
input: {
projectId: $projectId
itemId: $itemId
fieldId: $fieldId
value: { singleSelectOptionId: $optionId }
}
) {
clientMutationId
}
}
`,
{
projectId,
itemId,
fieldId: statusFieldId,
optionId: inProgressOptionId,
}
);

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +15
issues:
types: [assigned, reopened]
pull_request:
types: [opened]
branches: [main, develop]

jobs:
update-project-status:
runs-on: ubuntu-latest
steps:
- name: Move to In Progress on assignment
if: github.event_name == 'issues' && github.event.action == 'assigned'
uses: actions/github-script@v7
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

This workflow is configured to run on pull_request: opened and issues: reopened, but the only step is gated to issues + assigned, so PR-opened runs will be no-ops and reopened issues won't be handled. Either remove the unused triggers or add corresponding logic (and/or a job-level if) to match the intended behavior described in the PR.

Copilot uses AI. Check for mistakes.

jobs:
update-project-status:
runs-on: ubuntu-latest
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

This job mutates Projects/Issues via GraphQL; without explicit permissions (e.g., projects: write and issues: write as needed) it may fail in repos where the default GITHUB_TOKEN permission is read-only. Consider adding a permissions: block to the job for least-privilege and reliability.

Suggested change
runs-on: ubuntu-latest
runs-on: ubuntu-latest
permissions:
issues: write
projects: write

Copilot uses AI. Check for mistakes.

jobs:
auto-label:
runs-on: ubuntu-latest
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

This workflow writes labels via the GitHub API; consider adding an explicit permissions: block (at least issues: write) so it works reliably when repository default GITHUB_TOKEN permissions are restricted.

Suggested change
runs-on: ubuntu-latest
runs-on: ubuntu-latest
permissions:
issues: write

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +20
stale-issue-message: 'This issue has been inactive for 30 days. Please update or it will be closed in 7 days.'
stale-pr-message: 'This PR has been inactive for 30 days. Please update or it will be closed in 7 days.'
close-issue-message: 'Closing due to inactivity. Reopen if still needed.'
days-before-stale: 30
days-before-close: 7
exempt-issue-labels: 'epic,in-progress,priority-high'
exempt-pr-labels: 'in-review'
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

This workflow is named "Stale Issue Management" but it also configures stale PR handling (stale-pr-message and exempt-pr-labels) and days-before-close will close PRs as well unless explicitly limited. If the intent is issues-only (per PR description), set only-issues: true (or split into separate issue/PR configs) to avoid unexpectedly marking/closing PRs.

Copilot uses AI. Check for mistakes.
@morph-subagents
Copy link

🤖 Morph Preview Test

Preview URL: https://sobers-9zjy31mva-volvox-llc.vercel.app

AI Summary

Issues:

  • Navigation does not collapse into a hamburger menu at 375px; tabs remain visible and persistent in the header.

Verified:

  • Primary navigation links (Home, Program, Journey) are functional and route correctly.
  • Application layout and content remain accessible and usable at mobile widths.
  • Core functionality, including sobriety tracking and data rendering, remains stable following infrastructure updates.

Recording

Preview Recording

View full session →


Automated testing by Morph

@BillChirico BillChirico deleted the workflows-setup branch March 11, 2026 21:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants