Skip to content

Conversation

@shenxianpeng
Copy link
Contributor

@shenxianpeng shenxianpeng commented Sep 11, 2025

https://github.com/cpp-linter/cpp-linter-action/actions/runs/17632456845

Invalid workflow file: .github/workflows/stale.yml#L9
The workflow is not valid. .github/workflows/stale.yml (Line: 9, Col: 3): Error calling workflow 'cpp-linter/.github/.github/workflows/stale.yml@main'. The nested job 'stale' is requesting 'contents: read, pull-requests: write', but is only allowed 'contents: none, pull-requests: none'.

Summary by CodeRabbit

  • Chores
    • Defaulted CI workflow permissions to least-privilege for improved security; job-specific permissions preserved. Added a named workflow for CI lint checks and adjusted triggers to limit runs to the main branch and callable workflows.
  • Style
    • Minor formatting cleanup in workflow files.
  • Note
    • No user-facing functionality or UI changes in this release.

https://github.com/cpp-linter/cpp-linter-action/actions/runs/17632456845
```bash
Invalid workflow file: .github/workflows/stale.yml#L9
The workflow is not valid. .github/workflows/stale.yml (Line: 9, Col: 3): Error calling workflow 'cpp-linter/.github/.github/workflows/stale.yml@main'. The nested job 'stale' is requesting 'contents: read, pull-requests: write', but is only allowed 'contents: none, pull-requests: none'.
```
@shenxianpeng shenxianpeng requested a review from 2bndy5 September 11, 2025 06:47
@shenxianpeng shenxianpeng added the bug Something isn't working label Sep 11, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 11, 2025

Walkthrough

Added top-level permissions: {} to .github/workflows/stale.yml and adjusted triggers plus added a workflow name in .github/workflows/ci-check.yml; formatting whitespace was changed in stale.yml. No code logic or public APIs were modified.

Changes

Cohort / File(s) Summary
Workflow permissions
.github/workflows/stale.yml
Added top-level permissions: {} (default GitHub token permissions now empty); retained existing per-job permissions for the stale job; inserted a blank line before jobs: (formatting).
CI workflow triggers & metadata
.github/workflows/ci-check.yml
Added workflow name: "Lint CI workflows"; expanded on to include workflow_call; restricted push and pull_request triggers to branches: [main]; existing job steps left unchanged.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • add: statically analyze CI workflows #51 — Modifies the same GitHub Actions workflow files (.github/workflows/ci-check.yml, stale.yml) and overlaps on workflow metadata changes (names/triggers/permissions).

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "fix: add permissions section to stale.yml workflow" succinctly and accurately captures the primary change in the PR—adding a permissions section to .github/workflows/stale.yml to address a workflow validation error—making it specific, concise, and clear for reviewers scanning the history.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch shenxianpeng-patch-1

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.

Copy link

@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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be33864 and 83e683f.

📒 Files selected for processing (1)
  • .github/workflows/stale.yml (1 hunks)

Comment on lines +5 to +6
permissions: {}

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?

Copy link
Contributor

@2bndy5 2bndy5 left a comment

Choose a reason for hiding this comment

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

This will not fix the error reported in cpp-linter-action/.github/stale.yml.

The following permissions need to added to every calling workflow:

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

The permissions defined in reusable workflows seem to act like requirements needed in the calling workflow.

Copy link

@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: 0

🧹 Nitpick comments (3)
.github/workflows/ci-check.yml (3)

10-11: Be explicit about required permissions for reusable calls (or document caller requirements).

With on: workflow_call, callers must grant at least the permissions your jobs request. If a caller provides none, the job-level permissions: { contents: read } will trigger “nested job is requesting … but is only allowed none.” Consider either:

  • documenting that callers must set permissions (contents: read) on the calling job; or
  • declaring minimal top-level permissions here (e.g., permissions: { contents: read }) and removing the job-level override to keep it centralized.

Reusable workflow permission mismatches cause the exact error you saw. (kenmuse.com)


29-29: Broaden the glob to lint both .yml and .yaml files.

Current command skips .yaml files.

Apply:

-        run: pipx run zizmor --format=github .github/workflows/*.yml
+        run: pipx run zizmor --format=github ".github/workflows/*.{yml,yaml}"

26-29: Optional: pin zizmor for supply-chain hygiene.

pipx run zizmor pulls “latest” on each run. Pin a version and update intentionally.

Example:

-        run: pipx run zizmor --format=github ".github/workflows/*.{yml,yaml}"
+        run: pipx run "zizmor==0.8.2" --format=github ".github/workflows/*.{yml,yaml}"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 83e683f and a4573e1.

📒 Files selected for processing (1)
  • .github/workflows/ci-check.yml (1 hunks)
🔇 Additional comments (1)
.github/workflows/ci-check.yml (1)

1-1: Good: clear workflow name.

Adds discoverability in the Actions UI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants