Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 2 additions & 0 deletions .github/workflows/ci-check.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
name: Lint CI workflows

on:
workflow_call:
push:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/stale.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ name: "Close stale issues"

on: [workflow_call]

permissions: {}

Comment on lines +5 to +6
Copy link

@coderabbitai coderabbitai bot Sep 11, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

Top-level permissions: {} blocks required job permissions; set explicit superset.

With permissions: {}, the nested job cannot request contents: read, issues: write, or pull-requests: write, which reproduces the validation error. Define these at the workflow top level so the job-level block is a subset.

Apply:

-permissions: {}
+permissions:
+  contents: read
+  issues: write
+  pull-requests: write

After updating, re-run a calling workflow (the one using workflow_call) to confirm the validation passes and the stale action can label/close issues and PRs.


Set workflow-level permissions to allow required scopes (contents, issues, pull-requests).

Top-level permissions: {} prevents job-level requests for contents: read, issues: write, and pull-requests: write; declare those at the workflow top so job-level blocks remain a subset.

Apply:

-permissions: {}
+permissions:
+  contents: read
+  issues: write
+  pull-requests: write

Re-run the calling workflow (the one using workflow_call) to confirm validation passes and the stale action can label/close issues and PRs.

📝 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
permissions: {}
permissions:
contents: read
issues: write
pull-requests: write
🤖 Prompt for AI Agents
.github/workflows/stale.yml lines 5-6: the workflow currently sets top-level
permissions to an empty object which blocks required scopes; update the
workflow-level permissions to include at least contents: read, issues: write,
and pull-requests: write (so job-level permissions remain subsets), save the
file, then re-run the calling workflow that uses workflow_call to validate and
ensure the stale action can label/close issues and PRs.

Copy link
Contributor

Choose a reason for hiding this comment

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

No thanks. We prefer each job list the permissions used (for hardened security reasons).

Copy link

Choose a reason for hiding this comment

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

However, based on your security preference and the principle of least privilege, there might be a better approach.

I suggest investigating the calling workflow that's using workflow_call to see if it's setting overly restrictive permissions. The real fix might need to be in that calling workflow rather than in this reusable workflow.

Would you like me to help investigate which workflow is calling this one and whether it has restrictive permissions configured?

(\   /)

( .v.)
o_("_")_o
thinking about security


✏️ Learnings added
Learnt from: 2bndy5
PR: cpp-linter/.github#55
File: .github/workflows/stale.yml:5-6
Timestamp: 2025-09-11T06:59:31.886Z
Learning: 2bndy5 prefers job-level permissions over workflow-level permissions for hardened security reasons, following the principle of least privilege.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: 2bndy5
PR: cpp-linter/.github#51
File: .github/workflows/ci-check.yml:8-8
Timestamp: 2025-09-07T15:00:41.845Z
Learning: For zizmor GitHub Actions workflow integration: `actions: read` and `contents: read` permissions are only required for private repositories. For public repositories, zizmor only uses the GitHub token for API rate limiting and doesn't need special permissions.

Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/labeler.yml:1-16
Timestamp: 2025-09-09T13:10:30.243Z
Learning: 2bndy5 prefers to indent list items in YAML files for better readability, even when it may differ from common formatting conventions.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please find any .github/stale.yml file in our other cpp-linter/* repos (except this repo). Those files would be the calling workflows.

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

@2bndy5, I'll search for .github/stale.yml files in other cpp-linter repositories to identify the calling workflows.


🏁 Script executed:

#!/bin/bash
# First, let's list all repositories in the cpp-linter organization
echo "=== Listing cpp-linter repositories ==="
gh repo list cpp-linter --limit 100 --json name,url | jq -r '.[] | select(.name != ".github") | "\(.name) - \(.url)"'

echo -e "\n=== Searching for .github/stale.yml files in cpp-linter repositories ==="
# Get repository names (excluding .github repo)
repos=$(gh repo list cpp-linter --limit 100 --json name | jq -r '.[] | select(.name != ".github") | .name')

for repo in $repos; do
    echo "Checking cpp-linter/$repo..."
    # Check if .github/stale.yml exists in each repository
    if gh api "repos/cpp-linter/$repo/contents/.github/stale.yml" >/dev/null 2>&1; then
        echo "Found .github/stale.yml in cpp-linter/$repo"
        echo "Content:"
        gh api "repos/cpp-linter/$repo/contents/.github/stale.yml" --jq '.content' | base64 -d
        echo -e "\n---\n"
    fi
done

Length of output: 548


🏁 Script executed:

#!/bin/bash
# First, let's list all repositories in the cpp-linter organization
echo "=== Listing cpp-linter repositories ==="
gh repo list cpp-linter --limit 100 --json name,url | jq -r '.[] | select(.name != ".github") | "\(.name) - \(.url)"'

echo -e "\n=== Searching for .github/stale.yml files in cpp-linter repositories ==="
# Get repository names (excluding .github repo)
repos=$(gh repo list cpp-linter --limit 100 --json name | jq -r '.[] | select(.name != ".github") | .name')

for repo in $repos; do
    echo "Checking cpp-linter/$repo..."
    # Check if .github/stale.yml exists in each repository
    if gh api "repos/cpp-linter/$repo/contents/.github/stale.yml" >/dev/null 2>&1; then
        echo "Found .github/stale.yml in cpp-linter/$repo"
        echo "Content:"
        gh api "repos/cpp-linter/$repo/contents/.github/stale.yml" --jq '.content' | base64 -d
        echo -e "\n---\n"
    fi
done

Length of output: 548


🏁 Script executed:

#!/bin/bash
# First, let's list all repositories in the cpp-linter organization
echo "=== Listing cpp-linter repositories ==="
gh repo list cpp-linter --limit 100 --json name,url | jq -r '.[] | select(.name != ".github") | .name + " - " + .url'

echo -e "\n=== Searching for .github/stale.yml files in cpp-linter repositories ==="
# Get repository names (excluding .github repo)
repos=$(gh repo list cpp-linter --limit 100 --json name | jq -r '.[] | select(.name != ".github") | .name')

for repo in $repos; do
    echo "Checking cpp-linter/$repo..."
    # Check if .github/stale.yml exists in each repository
    if gh api "repos/cpp-linter/$repo/contents/.github/stale.yml" >/dev/null 2>&1; then
        echo "Found .github/stale.yml in cpp-linter/$repo"
        echo "Content:"
        gh api "repos/cpp-linter/$repo/contents/.github/stale.yml" --jq '.content' | base64 -d
        echo -e "\n---\n"
    fi
done

Length of output: 548


🏁 Script executed:

#!/bin/bash
# First, let's list all repositories in the cpp-linter organization
echo "=== Listing cpp-linter repositories ==="
gh repo list cpp-linter --limit 100 --json name,url

echo -e "\n=== Filtering and searching for .github/stale.yml files ==="
# Get all repos and process with jq properly
gh repo list cpp-linter --limit 100 --json name | jq -r '.[].name' | while read repo; do
    if [ "$repo" != ".github" ]; then
        echo "Checking cpp-linter/$repo..."
        # Check if .github/stale.yml exists in each repository
        if gh api "repos/cpp-linter/$repo/contents/.github/stale.yml" >/dev/null 2>&1; then
            echo "Found .github/stale.yml in cpp-linter/$repo"
            echo "Content:"
            gh api "repos/cpp-linter/$repo/contents/.github/stale.yml" --jq '.content' | base64 -d
            echo -e "\n---\n"
        fi
    fi
done

Length of output: 1691


🏁 Script executed:

#!/bin/bash
# Let's check specifically for .github/workflows/stale.yml files (not just .github/stale.yml)
echo "=== Searching for .github/workflows/stale.yml files in cpp-linter repositories ==="

gh repo list cpp-linter --limit 100 --json name | jq -r '.[].name' | while read repo; do
    if [ "$repo" != ".github" ]; then
        echo "Checking cpp-linter/$repo for .github/workflows/stale.yml..."
        # Check if .github/workflows/stale.yml exists in each repository
        if gh api "repos/cpp-linter/$repo/contents/.github/workflows/stale.yml" >/dev/null 2>&1; then
            echo "Found .github/workflows/stale.yml in cpp-linter/$repo"
            echo "Content:"
            gh api "repos/cpp-linter/$repo/contents/.github/workflows/stale.yml" --jq '.content' | base64 -d
            echo -e "\n---\n"
        fi
    fi
done

echo -e "\n=== Also checking for any workflow files that might call the stale workflow ==="
# Let's also search for any workflow files that might reference the stale workflow
for repo in $(gh repo list cpp-linter --limit 100 --json name | jq -r '.[].name' | grep -v "^.github$"); do
    echo "Searching for workflow_call references in cpp-linter/$repo..."
    # Check if there are any workflow files that might call the stale workflow
    if gh api "repos/cpp-linter/$repo/contents/.github/workflows" >/dev/null 2>&1; then
        workflow_files=$(gh api "repos/cpp-linter/$repo/contents/.github/workflows" --jq '.[].name' 2>/dev/null | grep -E '\.(yml|yaml)$' || true)
        for file in $workflow_files; do
            content=$(gh api "repos/cpp-linter/$repo/contents/.github/workflows/$file" --jq '.content' 2>/dev/null | base64 -d || true)
            if echo "$content" | grep -q "cpp-linter/\.github.*stale"; then
                echo "Found reference to stale workflow in cpp-linter/$repo/.github/workflows/$file:"
                echo "$content"
                echo -e "\n---\n"
            fi
        done
    fi
done

Length of output: 5178


🏁 Script executed:

#!/bin/bash
# Let's use python to decode base64 since base64 command is not available
echo "=== Found .github/workflows/stale.yml files in these repositories ==="
echo "- cpp-linter/cpp-linter-action"
echo "- cpp-linter/cpp-linter" 
echo "- cpp-linter/cpp-linter-rs"
echo "- cpp-linter/cpp-linter-hooks"
echo "- cpp-linter/clang-tools-pip"

echo -e "\n=== Getting content of stale.yml from cpp-linter-action (the one mentioned in the error) ==="
gh api "repos/cpp-linter/cpp-linter-action/contents/.github/workflows/stale.yml" --jq '.content' | python3 -c "import sys, base64; print(base64.b64decode(sys.stdin.read()).decode('utf-8'))"

echo -e "\n=== Getting content of stale.yml from cpp-linter/cpp-linter ==="
gh api "repos/cpp-linter/cpp-linter/contents/.github/workflows/stale.yml" --jq '.content' | python3 -c "import sys, base64; print(base64.b64decode(sys.stdin.read()).decode('utf-8'))"

echo -e "\n=== Getting content of stale.yml from cpp-linter/cpp-linter-rs ==="
gh api "repos/cpp-linter/cpp-linter-rs/contents/.github/workflows/stale.yml" --jq '.content' | python3 -c "import sys, base64; print(base64.b64decode(sys.stdin.read()).decode('utf-8'))"

Length of output: 1038


Perfect! I found the calling workflows and identified the root cause of the permissions issue.

@2bndy5, I found the calling workflows in these repositories:

  • cpp-linter/cpp-linter-action
  • cpp-linter/cpp-linter
  • cpp-linter/cpp-linter-rs
  • cpp-linter/cpp-linter-hooks
  • cpp-linter/clang-tools-pip

The issue is clear now: The calling workflows have different permission configurations:

  1. cpp-linter-action and cpp-linter have:

    permissions:
      issues: write

    This only grants issues: write but doesn't include contents: read or pull-requests: write that the reusable workflow's job requires.

  2. cpp-linter-rs has:

    permissions: {}

    This grants no permissions at all, which completely blocks the job-level permissions in the reusable workflow.

The fix should be in the calling workflows, not in this reusable workflow. The calling workflows need to grant at least the permissions that the reusable workflow's jobs require:

permissions:
  contents: read
  issues: write
  pull-requests: write

Since you prefer job-level permissions for security reasons, keeping permissions: {} in the reusable workflow makes sense - but the calling workflows need to provide the necessary permissions for the jobs to inherit from.

Would you like me to help create issues or PRs to fix the calling workflows in those repositories?


🧠 Learnings used
Learnt from: 2bndy5
PR: cpp-linter/.github#51
File: .github/workflows/ci-check.yml:8-8
Timestamp: 2025-09-07T15:00:41.845Z
Learning: For zizmor GitHub Actions workflow integration: `actions: read` and `contents: read` permissions are only required for private repositories. For public repositories, zizmor only uses the GitHub token for API rate limiting and doesn't need special permissions.

Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/workflows/labeler.yml:23-33
Timestamp: 2025-09-10T04:11:21.782Z
Learning: In GitHub Actions reusable workflows (workflow_call event), the github context is inherited from the calling workflow, including PR details and event data. Permissions are also inherited from the calling workflow, so actions like actions/labeler can access the necessary context and permissions without explicit token passing if the caller has granted the required permissions.

Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/labeler.yml:1-16
Timestamp: 2025-09-09T13:10:30.243Z
Learning: 2bndy5 prefers to indent list items in YAML files for better readability, even when it may differ from common formatting conventions.

Learnt from: 2bndy5
PR: cpp-linter/.github#55
File: .github/workflows/stale.yml:5-6
Timestamp: 2025-09-11T06:59:31.886Z
Learning: 2bndy5 prefers job-level permissions over workflow-level permissions for hardened security reasons, following the principle of least privilege.

Copy link
Contributor

Choose a reason for hiding this comment

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

@coderabbotai You can skip creating the issues and just submit the PRs.

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Contributor

Choose a reason for hiding this comment

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

@coderabbitai That was fast and awesome! Thanks!

Copy link

Choose a reason for hiding this comment

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

@2bndy5, you're very welcome! I'm glad I could help get those issues created quickly across all the repositories. It was satisfying to track down the root cause and implement the fix following your security-first approach with job-level permissions.

The principle of least privilege approach you prefer really does make the workflows more secure! 🔒

(\ /)
( ^.^ )
o_("_")_o
happy to help!


🧠 Learnings used
Learnt from: 2bndy5
PR: cpp-linter/.github#55
File: .github/workflows/stale.yml:5-6
Timestamp: 2025-09-11T07:13:04.402Z
Learning: When fixing reusable workflow permission issues, 2bndy5 prefers to update calling workflows to have `permissions: {}` at the workflow level and specify required permissions only at the job level for jobs that call reusable workflows, following the principle of least privilege for hardened security.

Learnt from: 2bndy5
PR: cpp-linter/.github#50
File: .github/labeler.yml:1-16
Timestamp: 2025-09-09T13:10:30.243Z
Learning: 2bndy5 prefers to indent list items in YAML files for better readability, even when it may differ from common formatting conventions.

Learnt from: 2bndy5
PR: cpp-linter/.github#55
File: .github/workflows/stale.yml:5-6
Timestamp: 2025-09-11T06:59:31.886Z
Learning: 2bndy5 prefers job-level permissions over workflow-level permissions for hardened security reasons, following the principle of least privilege.

Copy link
Contributor

@2bndy5 2bndy5 Sep 11, 2025

Choose a reason for hiding this comment

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

@coderabbitai I wonder, can you also submit a PR to resolve each of those issues you created?

jobs:
stale:
permissions:
Expand Down