Skip to content

feat(ci): add /assign GitHub Action with stale cleanup#2634

Open
Ayush7614 wants to merge 2 commits into
vybestack:mainfrom
Ayush7614:feat/assign-github-action-2630
Open

feat(ci): add /assign GitHub Action with stale cleanup#2634
Ayush7614 wants to merge 2 commits into
vybestack:mainfrom
Ayush7614:feat/assign-github-action-2630

Conversation

@Ayush7614

@Ayush7614 Ayush7614 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Implements the documented /assign self-assignment workflow (assign.yml + assign-issue.sh): exact-match comment gate, eligibility (merged PR / prior assignment / trusted contributors / owner-member-collaborator), 3-issue cap, auto-assigned label, and sticky feedback when assignment succeeds or silently fails.
  • Adds scheduled stale cleanup (assign-stale-cleanup.yml + unassign-stale-issues.sh) to unassign auto-assigned issues with no linked PR activity for 14 days (exempts acoliver).
  • Updates CONTRIBUTING.md and adds YAML/script contract tests in scripts/tests/assign-workflow.test.js.

Fixes #2630.

Test plan

  • bash -n on both scripts
  • bunx vitest run --config scripts/tests/vitest.config.ts scripts/tests/assign-workflow.test.js (11/11 passed)
  • Comment /assign on an unassigned eligible issue and confirm assignment + sticky feedback + auto-assigned label
  • Comment /assign on an already-assigned issue and confirm refusal feedback
  • Manually run Assign Stale Cleanup via workflow_dispatch after labeling a stale test issue

Summary by CodeRabbit

  • New Features

    • Added self-service issue assignment via an exact /assign comment.
    • Implemented eligibility checks, an assignment cap, success labeling, and sticky feedback messaging.
    • Added automated stale unassignment for auto-assigned after 14 days without linked PR activity, with a maintainer exemption.
    • Added daily scheduled and manual workflows to handle assignment and stale cleanup.
  • Documentation

    • Updated contribution guidelines with /assign requirements, eligibility/limits, and stale cleanup behavior.
  • Tests

    • Added Vitest coverage validating workflow configuration, gating logic, token/env mapping, and documentation expectations.

Implement the documented self-assignment slash command and a scheduled
cleanup for auto-assigned issues with no PR activity for two weeks.
Fixes vybestack#2630.
@Ayush7614
Ayush7614 requested a review from acoliver as a code owner July 22, 2026 15:19
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds /assign issue-comment automation with eligibility checks, assignment limits, labels, and sticky feedback. Adds daily stale-assignment cleanup after two weeks without linked PR activity, with workflow guards, documentation, and configuration-focused tests.

Changes

Issue Assignment Automation

Layer / File(s) Summary
Assignment eligibility and execution
.github/scripts/assign-issue.sh, scripts/tests/assign-workflow.test.js
Validates inputs, checks assignment eligibility and limits, assigns eligible commenters, applies the auto-assigned label, and posts sticky feedback.
Assign workflow and documented contract
.github/workflows/assign.yml, CONTRIBUTING.md, scripts/tests/assign-workflow.test.js
Triggers on exact /assign comments, passes issue context to the script, and documents the assignment rules and feedback behavior.
Stale assignment detection and removal
.github/scripts/unassign-stale-issues.sh
Finds stale labeled assignments, checks assignment age and linked PR activity, preserves the exempt account, and removes eligible assignments and labels.
Scheduled cleanup workflow validation
.github/workflows/assign-stale-cleanup.yml, scripts/tests/assign-workflow.test.js
Adds daily and manual cleanup triggers, repository gating, scoped permissions, and assertions for the cleanup workflow and script configuration.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested labels: maintainer:e2e:ok

Suggested reviewers: acoliver

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: a /assign GitHub Action with stale cleanup.
Description check ✅ Passed The description covers the main changes and test plan, so it is sufficiently complete despite some missing template sections.
Linked Issues check ✅ Passed The implementation matches #2630: exact /assign matching, eligibility checks, and 14-day stale auto-unassignment with acoliver exempt.
Out of Scope Changes check ✅ Passed The workflows, scripts, docs, and tests all support the /assign and stale-cleanup goals; no unrelated scope stands out.
📋 Issue Planner

Built with CodeRabbit's Coding Plans for faster development and fewer bugs.

View plan used: #2630

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Checkov (3.3.8)
.github/workflows/assign-stale-cleanup.yml

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

.github/workflows/assign.yml

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'


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.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

LLxprt PR Review – PR #2634

Issue Alignment

Implements the /assign self-assignment workflow and 14-day stale cleanup described in #2630. The assign workflow enforces exact-match /assign, eligibility checks (merged PR, prior assignment, trusted contributors, owner/member/collaborator), a 3-issue cap, auto-assigned label, and sticky feedback. The stale cleanup unassigns auto-assigned issues with no linked PR activity after 14 days and exempts acoliver. Documentation in CONTRIBUTING.md is updated accordingly. Alignment is strong.

Side Effects

  • Adds two new GitHub Actions workflows (assign.yml on issue_comment, assign-stale-cleanup.yml on schedule) that modify issue assignees/labels/comments.
  • The stale workflow restricts scheduled runs to vybestack/llxprt-code, which is good.
  • Uses gh search prs and gh search issues which may be rate-limited on large repos, but this is acceptable for a cleanup job.
  • No breaking changes to existing code; only new automation.

Code Quality

  • Scripts use set -euo pipefail, validate required inputs, and handle missing tokens.
  • assign-issue.sh correctly guards against already-assigned issues, caps concurrent assignments, checks eligibility, and handles write-access failures with user-facing feedback.
  • unassign-stale-issues.sh includes retry logic (retry_gh), timeline-based assignment age detection with fallback, and exempts acoliver.
  • Workflows declare least-privilege permissions (contents: read, issues: write, pull-requests: read).
  • Minor concern: assign-issue.sh runs gh label create on every assignment; this is harmless due to || true, but slightly noisy.

Tests and Coverage

Coverage impact: unknown / effectively no behavioral coverage.

  • scripts/tests/assign-workflow.test.js validates YAML structure, exact-match gating, concurrency config, permissions, and script string contents.
  • These are static contract checks, not behavioral tests. They do not execute the scripts, simulate GitHub events, or verify eligibility logic end-to-end.
  • Per the safety gate, this PR is not documentation-only and lacks meaningful automated tests. It should be returned for remediation.

Verdict

Needs Work
The implementation matches the issue requirements and is reasonably well-structured, but the test suite is insufficient for non-documentation changes. Add meaningful automated tests (e.g., script execution under mocked gh, workflow behavior tests, or integration tests) before merge.

Comment on lines +150 to +156
# Exempt: never unassign acoliver (ticket exception).
for login in "${ASSIGNEES[@]}"; do
if [[ "${login}" == "${EXEMPT_LOGIN}" ]]; then
echo " 🛡️ Skipping #${issue_number}: assignee @${EXEMPT_LOGIN} is exempt"
return 0
fi
done

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The acoliver exemption returns for the entire issue, so if acoliver is one of multiple assignees, other stale assignees are never evaluated for unassignment. If the intent is only to protect acoliver, move the exemption check inside the per-assignee loop so other assignees can still be cleaned up independently.

Comment on lines +210 to +216
mapfile -t CANDIDATES < <(
gh issue list --repo "${REPO}" --state open --label "${AUTO_ASSIGNED_LABEL}" \
--limit 200 \
--json number,assignees \
--jq '.[] | "\(.number)\t\([.assignees[].login] | join(","))"' \
2>/dev/null || true
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The candidate issue list is fetched with gh issue list --limit 200 and no pagination. If the repository has more than 200 open issues with the auto-assigned label, stale issues beyond that window are never processed, resulting in incomplete cleanup. Consider adding --paginate or iterating pages to ensure all candidates are scanned.

Comment on lines +64 to +67
assignee_count="$(
gh issue view "${ISSUE_NUMBER}" --repo "${REPO}" --json assignees \
--jq '.assignees | length' 2>/dev/null || echo "0"
)"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Redundant API call: the script invokes gh issue view twice to retrieve assignees—first to count them (.assignees | length) and then to list their logins ([.assignees[].login] | join(", ")). Combine these into a single API call to reduce latency and GitHub API usage. For example, fetch the full assignee list once, compute the count via ${#assignees[@​]}, and extract logins from the same response.

Comment thread .github/scripts/assign-issue.sh Outdated

require_env GITHUB_REPOSITORY
require_env ISSUE_NUMBER
require_env COMMENTER_LOGIN

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Missing input validation for COMMENTER_LOGIN: the script does not validate that the login follows GitHub username conventions (alphanumeric + hyphens, no consecutive hyphens, max 39 characters). While GitHub generally validates this upstream, adding a defensive check prevents unexpected behavior in API calls or future modifications that might interpolate the variable into queries.

Comment on lines +37 to +45
- name: Run unassign-stale-issues script
env:
GH_TOKEN: ${{ github.token }}
GITHUB_TOKEN: ${{ github.token }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
set -euo pipefail
chmod +x ./.github/scripts/unassign-stale-issues.sh
./.github/scripts/unassign-stale-issues.sh

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The gh issue list command is limited to 200 results (--limit 200). If the repository ever has more than 200 open issues with the auto-assigned label, some stale issues will be silently skipped. Consider removing the limit or using a pagination strategy to ensure all candidates are evaluated.


function loadWorkflow(relPath) {
const source = readRootFile(relPath);
const parsed = yaml.load(source);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The test uses yaml.load(source) without specifying a safe schema, which defaults to the unsafe YAML schema that can execute arbitrary JavaScript through custom tags. While the YAML files are trusted repository files, this is a security anti-pattern that could lead to code execution if the test is ever reused with untrusted input. Use yaml.load(source, { schema: yaml.SAFE_SCHEMA }) or yaml.load(source, { schema: yaml.JSON_SCHEMA }) instead.

Comment on lines +127 to +131
expect(script).toContain('trusted-contributors.txt');
expect(script).toContain('gh search prs');
expect(script).toContain('--merged');
expect(script).toContain('--add-assignee');
expect(script).toContain('post_sticky_feedback');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These tests only verify the presence of strings in the shell scripts, not the actual behavior. A test could pass even if the string appears in a comment or the functionality is broken. Consider testing the actual behavior (e.g., by mocking environment variables and verifying outputs, or at minimum testing that the strings appear in executable code paths rather than comments).

Comment on lines +97 to +99
expect(normalize(job?.if)).toContain(
"github.repository == 'vybestack/llxprt-code'",
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Consider extracting repeated hardcoded business values into constants at the top of the test file. This creates maintenance overhead and makes it harder to update these values in the future. For example: const CANONICAL_REPO = 'vybestack/llxprt-code'; const EXEMPT_LOGIN = 'acoliver'; const CRON_SCHEDULE = '0 7 * * *'; const STALE_WINDOW_DAYS = 14; const MAX_ASSIGNMENTS = 3;

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

OpenCodeReview — PR #2634

  • Reviewed head SHA: 27cb3868e72f40b951cd3ab539191c91426f37ad
  • Merge base: 83bd764ea3f3418bc918fe86faa283b21179d7c0
  • OCR version: open-code-review v1.7.9 (a32f852) linux/amd64 built at: 2026-07-14T03:41:57Z https://github.com/alibaba/open-code-review
  • Phase: review
  • Exit code: 0
  • Run: https://github.com/vybestack/llxprt-code/actions/runs/29993465088
  • 5 finding(s) (5 posted inline).
  • Artifacts: ocr-review-output contains raw JSON, stdout, stderr, preview, phase, and exit-code diagnostics.

@acoliver

Copy link
Copy Markdown
Collaborator

@Ayush7614 run shellcheck

@acoliver

Copy link
Copy Markdown
Collaborator

@CodeRabbit is the shell necessary in here or could this be done entirely in yaml?

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== Candidate files =='
fd -a '^(assign\.yml|assign-issue\.sh|assign-stale-cleanup\.yml|unassign-stale-issues\.sh|assign-workflow\.test\.js)$' . \
  -E node_modules -E dist -E build

printf '%s\n' '== Workflow outlines =='
for f in $(fd -a '^(assign\.yml|assign-stale-cleanup\.yml)$' . -E node_modules -E dist -E build); do
  echo "--- $f"
  nl -ba "$f"
done

printf '%s\n' '== Script function outlines =='
for f in $(fd -a '^(assign-issue\.sh|unassign-stale-issues\.sh)$' . -E node_modules -E dist -E build); do
  echo "--- $f"
  rg -n '^(#!/|[[:alpha:]_][[:alnum:]_]*\(\)|[[:alpha:]_][[:alnum:]_]*\(\s*\{)|\b(gh|jq|curl)\b|case |if |while |for )' "$f" || true
done

Length of output: 984


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== Repository change summary =='
git status --short
git diff --name-status HEAD~1..HEAD 2>/dev/null || true

printf '%s\n' '== /assign implementation references =='
rg -n -i -C 3 '/assign|auto-assigned|stale cleanup|unassign.*stale' \
  -g '*.yml' -g '*.yaml' -g '*.sh' -g 'assign-workflow.test.js' . \
  -g '!node_modules/**' -g '!dist/**' -g '!build/**' || true

printf '%s\n' '== Contract test =='
test_file="$(fd -a '^assign-workflow\.test\.js$' scripts/tests | head -n 1)"
if [ -n "$test_file" ]; then
  nl -ba "$test_file"
fi

Length of output: 8443


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in \
  .github/workflows/assign.yml \
  .github/workflows/assign-stale-cleanup.yml \
  .github/scripts/assign-issue.sh \
  .github/scripts/unassign-stale-issues.sh
do
  if [ -f "$f" ]; then
    printf '\n%s\n' "===== $f ====="
    awk '{ printf "%4d  %s\n", NR, $0 }' "$f"
  else
    printf '%s\n' "MISSING: $f"
  fi
done

Length of output: 20136


@acoliver No—this cannot be done entirely in declarative GitHub Actions YAML.

YAML is appropriate for the triggers, exact-comment gate, permissions, concurrency, and scheduling. But the implementation also needs dynamic GitHub API work and control flow:

  • query merged PRs, prior assignments, and open-assignment counts;
  • find/update the sticky feedback comment;
  • verify that GitHub actually applied an assignment;
  • enumerate labeled issues and assignees;
  • inspect assignment timestamps and linked PR activity;
  • calculate the 14-day threshold, retry failed API calls, and loop over candidates.

Those require imperative code. The current checked-in shell scripts are a reasonable choice, particularly since the logic is independently contract-tested.

The alternative would be actions/github-script with embedded JavaScript/Octokit calls. That could remove the shell scripts (and likely checkout/chmod), but it would not be YAML-only—it would move the same complexity into inline JavaScript, which is generally less maintainable and harder to test than the present scripts.

@acoliver

Copy link
Copy Markdown
Collaborator

@CodeRabbit is there an attack vector here and if so can we limit it? thinking like 1000 bots smash us with /assign ?

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

workflow="$(fd -a '^assign\.yml$' . -E node_modules -E dist -E build | head -n1)"
script="$(fd -a '^assign-issue\.sh$' . -E node_modules -E dist -E build | head -n1)"
test_file="$(fd -a '^assign-workflow\.test\.js$' . -E node_modules -E dist -E build | head -n1)"

printf '%s\n' '== Candidate paths =='
printf 'workflow=%s\nscript=%s\ntest=%s\n' "$workflow" "$script" "$test_file"

for file in "$workflow" "$script" "$test_file"; do
  if [ -n "$file" ] && [ -f "$file" ]; then
    printf '\n===== %s =====\n' "$file"
    nl -ba "$file"
  fi
done

Length of output: 1095


🏁 Script executed:

#!/bin/bash
set -euo pipefail

find . \
  -path '*/node_modules' -prune -o \
  -path '*/dist' -prune -o \
  -path '*/build' -prune -o \
  -type f \( \
    -name 'assign.yml' -o \
    -name 'assign-issue.sh' -o \
    -name 'assign-workflow.test.js' \
  \) -print |
while IFS= read -r file; do
  printf '\n===== %s =====\n' "$file"
  sed -n '=' "$file" | sed 'N;s/\n/\t/'
done

Length of output: 1959


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for file in \
  .github/workflows/assign.yml \
  .github/scripts/assign-issue.sh \
  scripts/tests/assign-workflow.test.js
do
  if [ -f "$file" ]; then
    printf '\n===== %s =====\n' "$file"
    sed -n '1,260p' "$file"
  else
    printf 'MISSING: %s\n' "$file"
  fi
done

Length of output: 14287


@acoliver Yes. The permission boundary is reasonably narrow—an untrusted bot cannot gain repository write access or bypass GitHub’s assignee rules—but there is a workflow/API-consumption denial-of-service vector:

  • Every exact /assign issue comment starts a runner.
  • The script checks out the repository, performs several gh API/search requests, and may update the sticky feedback comment before rejecting an ineligible account.
  • The current concurrency key is per issue, so it coalesces spam on one issue, but 1,000 bots commenting on 1,000 issues can still create up to 1,000 jobs.

I would limit this in two layers:

  1. Repository-wide concurrency for this workflow, rather than per-issue concurrency. This creates a hard upper bound on active/pending /assign work and lets GitHub discard superseded queued spam. The tradeoff is that legitimate assignment requests may need to be reposted if a burst occurs.

    concurrency:
      group: ${{ github.workflow }}
      cancel-in-progress: false
  2. Fail closed as early as possible: add a job-level gate for repository-associated users where compatible with the intended policy, then keep the script’s authoritative eligibility checks. At minimum, put the cheap AUTHOR_ASSOCIATION check before the current assignment-count and search queries. Right now an unknown account consumes several API calls before it is denied.

Also, for failed eligibility, I would avoid posting/updating sticky feedback for every request—or only provide that feedback for known contributors. Otherwise attackers can deliberately generate persistent issue-comment noise and write API traffic.

The three-assignment cap protects issue ownership; it does not rate-limit invocation volume. I recommend treating global concurrency plus early rejection as required hardening before enabling this on a public repository.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/scripts/assign-issue.sh:
- Around line 95-137: Update the workflow permissions associated with the
assign-issue script to include pull-requests: read alongside the existing
permissions. Ensure the gh search prs invocation in the merged_pr_count
eligibility check can read pull requests and preserve the current eligibility
behavior.

In @.github/scripts/unassign-stale-issues.sh:
- Around line 148-166: Update the assignee evaluation in the function containing
the exemption loop so the `EXEMPT_LOGIN` check occurs inside the per-assignee
loop. Skip only `acoliver` while continuing to evaluate other co-assignees
through `assignment_age_ok_to_unassign`; remove the issue-level `return 0`
behavior and preserve the existing unassignment decision flow.

In @.github/workflows/assign.yml:
- Around line 11-13: Update the workflow permissions block in assign.yml to add
pull-requests: read alongside contents: read and issues: write, allowing the
merged pull-request search used by the workflow to function correctly.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1f3d8a5f-64b7-4b6f-a790-aa65ed95e103

📥 Commits

Reviewing files that changed from the base of the PR and between 83bd764 and b89c0a9.

📒 Files selected for processing (6)
  • .github/scripts/assign-issue.sh
  • .github/scripts/unassign-stale-issues.sh
  • .github/workflows/assign-stale-cleanup.yml
  • .github/workflows/assign.yml
  • CONTRIBUTING.md
  • scripts/tests/assign-workflow.test.js

Comment on lines +95 to +137
merged_pr_count="$(
gh search prs --repo "${REPO}" --author "${USER_LOGIN}" --merged \
--json number --jq 'length' 2>/dev/null || echo "0"
)"

if [[ "${merged_pr_count}" -gt 0 ]]; then
is_eligible=true
eligibility_reason="prior merged PR(s)"
fi

if [[ "${is_eligible}" != "true" ]]; then
prior_assigned_count="$(
gh search issues --repo "${REPO}" --assignee "${USER_LOGIN}" --state all \
--json number --jq 'length' 2>/dev/null || echo "0"
)"
if [[ "${prior_assigned_count}" -gt 0 ]]; then
is_eligible=true
eligibility_reason="prior issue assignment"
fi
fi

if [[ "${is_eligible}" != "true" && -f "${TRUSTED_FILE}" ]]; then
if grep -Fqx "${USER_LOGIN}" "${TRUSTED_FILE}"; then
is_eligible=true
eligibility_reason="trusted contributor list"
fi
fi

# Owners/members/collaborators are always eligible (author_association not
# always available here; treat repository permission via association env).
AUTHOR_ASSOCIATION="${AUTHOR_ASSOCIATION:-}"
case "${AUTHOR_ASSOCIATION}" in
OWNER | MEMBER | COLLABORATOR)
is_eligible=true
eligibility_reason="repository ${AUTHOR_ASSOCIATION}"
;;
esac

if [[ "${is_eligible}" != "true" ]]; then
echo "⚠️ @${USER_LOGIN} is not eligible for self-assignment"
post_sticky_feedback "❌ Could not assign @${USER_LOGIN}: eligibility requires at least one merged PR in this repository, a prior issue assignment, or membership in \`.github/trusted-contributors.txt\`. Please open a PR first or ask a maintainer to assign you."
exit 0
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does GitHub Actions GITHUB_TOKEN need pull-requests read permission for gh search prs / search API reads on a public repository, or are public repo reads exempt from the permissions block?

💡 Result:

Yes, you need to explicitly grant pull-requests: read permission if you use the permissions block in your workflow file and intend to use gh search prs or similar API calls that require those scopes [1][2]. There is no exemption for public repositories regarding the permissions block [3][1]. When you define a permissions block in your GitHub Actions workflow file, you must explicitly state all the permissions you require [1][2]. Any permission not explicitly listed in that block is set to none (with the exception of metadata, which always receives read access) [1][2]. While public repositories may have different default GITHUB_TOKEN settings compared to private ones depending on your organization or repository configuration [4][5], these defaults only apply when you do not define a custom permissions block [4][6]. Once you include a permissions key in your YAML file, the default permissions are overridden by your explicit configuration [3][1][2]. Therefore, if your workflow needs to search for pull requests, you must include pull-requests: read in your permissions configuration [1][2].

Citations:


Add pull-requests: read to the workflow permissions. gh search prs runs under the workflow’s explicit permissions: block, so contents: read and issues: write aren’t enough. Without pull-requests: read, the merged-PR eligibility path falls back to 0 and rejects valid commenters.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/assign-issue.sh around lines 95 - 137, Update the workflow
permissions associated with the assign-issue script to include pull-requests:
read alongside the existing permissions. Ensure the gh search prs invocation in
the merged_pr_count eligibility check can read pull requests and preserve the
current eligibility behavior.

Comment thread .github/scripts/unassign-stale-issues.sh
Comment thread .github/workflows/assign.yml
@acoliver

Copy link
Copy Markdown
Collaborator

@Ayush7614 look at #2634 (comment) and the other coderabbit reviews. LLet's assume at some point someone will comment /assign on everything so let's add a cap for how many issues they can be assigned at once, the concurrency cap. ask your agent for any other hardening recommendations to prevent also spammers (github name BuyMyStupidThing /assign every issue it can)

Satisfy shellcheck, add pull-requests:read, reject Bot actors, serialize
per-actor concurrency, keep the 3-issue cap, and unassign stale
co-assignees without skipping whole issues for acoliver.
@Ayush7614

Copy link
Copy Markdown
Contributor Author

@acoliver addressed shellcheck + the spam-hardening notes (and CodeRabbit’s actionable items):

CI / shellcheck

  • Fixed SC2154 / SC2269 / SC2249 locally with shellcheck 0.11.0 (same flags as CI).

Spam / flood hardening

  • Hard 3 open-issue concurrency cap (already present; kept + documented more clearly).
  • Workflow now ignores Bot accounts.
  • Concurrency group is now per issue + actor, so a flood of /assign from one user cancels/queues on itself instead of fan-outing.
  • Eligibility still requires merged PR / prior assignment / trusted list / owner-member-collaborator before assign.
  • Non-collaborators still fail the post-assign verify and get sticky feedback (no silent “success”).

CodeRabbit

  • Added pull-requests: read so gh search prs --merged works under the explicit permissions block.
  • Stale cleanup now exempts only acoliver as an assignee (other co-assignees can still be unassigned).
  • Dropped the 50-PR list fallback; linked-PR detection uses search only.

Happy to add more rate-limiting (e.g. daily attempt budget) if you want that next.

Comment on lines +65 to +73
if [[ "${assignee_count}" -gt 0 ]]; then
current="$(
gh issue view "${ISSUE}" --repo "${REPO}" --json assignees \
--jq '[.assignees[].login] | join(", ")' 2>/dev/null || echo "someone"
)"
echo "⚠️ Issue #${ISSUE} is already assigned to: ${current}"
post_sticky_feedback "❌ Could not assign @${USER_LOGIN}: this issue is already assigned to **${current}**."
exit 0
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Race condition in assignment logic due to concurrent workflow runs from different actors. The workflow uses per-issue-per-actor concurrency, so different users assigning the same issue can still execute in parallel. Both will pass the "issue unassigned" check, both will attempt assignment, and both will call post_sticky_feedback, potentially creating duplicate sticky comments. Consider adding a global per-issue lock (e.g., using a GitHub API lock file or issue comment lock) or at least handling the case where the issue becomes assigned between the check and the mutation.

Comment on lines +161 to +165
gh label create "${AUTO_ASSIGNED_LABEL}" \
--repo "${REPO}" \
--color "0E8A16" \
--description "Assigned via /assign automation" \
>/dev/null 2>&1 || true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

gh label create is invoked unconditionally on every assignment, even when the label already exists. This generates unnecessary API calls and consumes rate limit quota. Use an idempotent check like gh label view first, or use gh label edit which creates or updates the label in a single call.

Comment on lines +105 to +115
pr_hits="$(
gh search prs \
--repo "${REPO}" \
--author "${assignee}" \
--json number \
--jq 'length' \
"#${issue_number} in:title,body" \
2>/dev/null || echo "0"
)"

[[ "${pr_hits}" =~ ^[0-9]+$ ]] && [[ "${pr_hits}" -gt 0 ]]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This script calls gh search prs, which requires the GitHub Search API scope, but the workflow does not grant search permission (assign-stale-cleanup.yml has contents: read, issues: write, pull-requests: read only). Additionally, errors are swallowed with || echo "0", so on permission denial, rate limiting, or API unavailability the script will incorrectly conclude there is no linked PR activity and proceed to unassign users. Please either add search: read to the workflow permissions or add explicit failure handling here (and avoid silently treating errors as zero hits).

Comment on lines +72 to +78
if [[ -z "${assigned_at}" ]]; then
# Fall back to issue updatedAt when timeline is unavailable.
assigned_at="$(
gh issue view "${issue_number}" --repo "${REPO}" --json updatedAt \
--jq '.updatedAt' 2>/dev/null || true
)"
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The updatedAt fallback is semantically wrong for assignment age: updatedAt changes on any issue mutation, including comments or label changes, so it can prevent stale cleanup for an old assignment with recent unrelated activity. If a reliable assignment timestamp is needed, store or derive it from an explicit assignment event/comment rather than using generic issue update time.

Comment on lines +192 to +198
mapfile -t CANDIDATES < <(
gh issue list --repo "${REPO}" --state open --label "${AUTO_ASSIGNED_LABEL}" \
--limit 200 \
--json number,assignees \
--jq '.[] | "\(.number)\t\([.assignees[].login] | join(","))"' \
2>/dev/null || true
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The candidate issue scan is capped at 200 issues (gh issue list --limit 200). If the repository ever has more than 200 open issues with the auto-assigned label, the remainder will be skipped during each run. Please either increase the limit to the maximum supported by GitHub, paginate through results, or document this as a known limitation with a remediation plan.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.github/scripts/assign-issue.sh (2)

87-133: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not grant self-assignment eligibility from repository roles or the trusted file.

The eligibility block accepts OWNER, MEMBER, COLLABORATOR, and .github/trusted-contributors.txt membership without a merged PR or prior issue assignment, while the failure feedback only lists those two conditions plus the trusted file. Remove the role bypass or document and test the role/trusted-file exceptions as intentional policy.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/assign-issue.sh around lines 87 - 133, Restrict
self-assignment eligibility in the eligibility logic around merged_pr_count and
prior_assigned_count to users with a merged PR or prior issue assignment. Remove
the TRUSTED_FILE membership check and the AUTHOR_ASSOCIATION role bypass,
including their eligibility reasons, so OWNER, MEMBER, COLLABORATOR, and
trusted-file entries cannot bypass those requirements.

137-169: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make assignment and tracking-label updates a single failure domain.

If gh issue edit --add-label fails, or the run is canceled between the assignment and label write, the issue stays assigned without auto-assigned. The stale cleanup workflow selects open issues by label:auto-assigned, so this leave-behind assignment will not be discovered and may run against the per-user cap. Use one API update that sets the assignee and preserves existing labels, and report failure unless both states persist before printing success.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/assign-issue.sh around lines 137 - 169, The assignment and
auto-assigned label writes in the post-eligibility flow must be one atomic
tracking update. Replace the separate gh issue edit calls around the assignment
verification and label addition with a single API update that sets USER_LOGIN as
assignee while preserving all existing labels, then re-read the issue and report
failure unless both the assignee and AUTO_ASSIGNED_LABEL are present; only print
success after both states persist.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/scripts/assign-issue.sh:
- Around line 39-43: Update the existing_id lookup in the issue-assignment flow
to capture gh api output and its exit status separately, rather than masking
failures with || true. Fail closed when the API request returns non-zero, while
preserving the current behavior when the request succeeds but no comment matches
MARKER.
- Around line 40-42: Update the comment lookup in the assignment feedback flow
to match the marker only when the comment was authored by the configured bot
account, preventing user-authored marker copies from being patched. Preserve
pagination and first-match behavior, and use the existing bot identity
configuration rather than hardcoding an unrelated author.
- Around line 60-68: Serialize the entire assignment check-and-mutation flow in
the `/assign` workflow per issue, rather than per commenter, by updating the
concurrency group in the workflow configuration and setting cancel-in-progress
to false. Ensure the existing zero-assignee check and add-assignee operation in
the assignment script execute within that shared per-issue queue.
- Around line 60-63: Update the assignee count read in the assign guard to fail
closed: remove the fallback that converts gh issue view errors into 0, capture
or propagate the command failure, and abort with clear feedback when reading
current assignees fails. Continue to the assignment flow only when
assignee_count is successfully obtained.

---

Outside diff comments:
In @.github/scripts/assign-issue.sh:
- Around line 87-133: Restrict self-assignment eligibility in the eligibility
logic around merged_pr_count and prior_assigned_count to users with a merged PR
or prior issue assignment. Remove the TRUSTED_FILE membership check and the
AUTHOR_ASSOCIATION role bypass, including their eligibility reasons, so OWNER,
MEMBER, COLLABORATOR, and trusted-file entries cannot bypass those requirements.
- Around line 137-169: The assignment and auto-assigned label writes in the
post-eligibility flow must be one atomic tracking update. Replace the separate
gh issue edit calls around the assignment verification and label addition with a
single API update that sets USER_LOGIN as assignee while preserving all existing
labels, then re-read the issue and report failure unless both the assignee and
AUTO_ASSIGNED_LABEL are present; only print success after both states persist.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 472bf4a3-4bb3-470f-8835-e583148e1084

📥 Commits

Reviewing files that changed from the base of the PR and between b89c0a9 and 27cb386.

📒 Files selected for processing (6)
  • .github/scripts/assign-issue.sh
  • .github/scripts/unassign-stale-issues.sh
  • .github/workflows/assign-stale-cleanup.yml
  • .github/workflows/assign.yml
  • CONTRIBUTING.md
  • scripts/tests/assign-workflow.test.js
🚧 Files skipped from review as they are similar to previous changes (5)
  • .github/workflows/assign-stale-cleanup.yml
  • .github/workflows/assign.yml
  • CONTRIBUTING.md
  • scripts/tests/assign-workflow.test.js
  • .github/scripts/unassign-stale-issues.sh

Comment on lines +39 to +43
existing_id="$(
gh api "repos/${REPO}/issues/${ISSUE}/comments" --paginate \
--jq ".[] | select(.body != null and (.body | contains(\"${MARKER}\"))) | .id" 2>/dev/null \
| head -n 1 || true
)"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "File exists:"
test -f .github/scripts/assign-issue.sh && echo yes || echo no

echo
echo "Relevant script section:"
sed -n '1,90p' .github/scripts/assign-issue.sh | cat -n

echo
echo "Shell behavior for set -e with pipeline containing gh api && head with failed command:"
set -euo pipefail
bash -c 'false || true' && echo "pipeline_with_trued_succeeded" || echo "pipeline_with_trued_failed"
bash -c 'set -euo pipefail; false | true' && echo "pipefail_pipeline_succeeded" || echo "pipefail_pipeline_failed"
printf '1\n2\n' | false | head -n 1 && echo "pipe_before_head_succeeded" || echo "pipe_before_head_failed"

Repository: vybestack/llxprt-code

Length of output: 3983


Handle gh api lookup failures separately from missing comments.

|| true hides the exit status of gh api, so when the API read fails assignment can proceed and post another comment instead of failing closed. Capture the list first, then distinguish non-zero API failures from an absent sticky match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/assign-issue.sh around lines 39 - 43, Update the existing_id
lookup in the issue-assignment flow to capture gh api output and its exit status
separately, rather than masking failures with || true. Fail closed when the API
request returns non-zero, while preserving the current behavior when the request
succeeds but no comment matches MARKER.

Comment on lines +40 to +42
gh api "repos/${REPO}/issues/${ISSUE}/comments" --paginate \
--jq ".[] | select(.body != null and (.body | contains(\"${MARKER}\"))) | .id" 2>/dev/null \
| head -n 1 || true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file exists and relevant lines =="
if [ -f .github/scripts/assign-issue.sh ]; then
  sed -n '1,120p' .github/scripts/assign-issue.sh | nl -ba
else
  echo ".github/scripts/assign-issue.sh not found"
  fd -a 'assign-issue\.sh' .
fi

echo
echo "== searches for markers/comments assign issues =="
rg -n "MARKER|create comment|assign issue|sticky|comments|assign_issue|assign-issue" .github scripts . 2>/dev/null | head -200

Repository: vybestack/llxprt-code

Length of output: 236


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file exists and relevant lines =="
if [ -f .github/scripts/assign-issue.sh ]; then
  sed -n '1,120p' .github/scripts/assign-issue.sh
else
  echo ".github/scripts/assign-issue.sh not found"
  find . -name 'assign-issue.sh' -print
fi

echo
echo "== searches for markers/comments assign issues =="
grep -RInE "MARKER|create comment|assign issue|sticky|comments|assign_issue|assign-issue" .github scripts . 2>/dev/null | head -200 || true

Repository: vybestack/llxprt-code

Length of output: 30029


Restrict sticky updates to bot-authored comments.

The selector matches any comment containing <!-- llxprt-assign-feedback -->, including a user-supplied copy of the marker, so gh api --method PATCH can overwrite that comment. Select the bot’s existing comment (for example by author login) or store the created comment ID.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/assign-issue.sh around lines 40 - 42, Update the comment
lookup in the assignment feedback flow to match the marker only when the comment
was authored by the configured bot account, preventing user-authored marker
copies from being patched. Preserve pagination and first-match behavior, and use
the existing bot identity configuration rather than hardcoding an unrelated
author.

Comment on lines +60 to +63
assignee_count="$(
gh issue view "${ISSUE}" --repo "${REPO}" --json assignees \
--jq '.assignees | length' 2>/dev/null || echo "0"
)"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate file =="
git ls-files | rg '(^|/)\.github/scripts/assign-issue\.sh$|assign-issue\.sh$' || true

echo "== File excerpt =="
if [ -f .github/scripts/assign-issue.sh ]; then
  nl -ba .github/scripts/assign-issue.sh | sed -n '1,130p'
fi

echo "== GitHub issue assignees docs excerpt from network (if gh/curl ok) =="
if command -v gh >/dev/null 2>&1; then
  gh api repos/github/docs-content/content/rest/issues/assignees.md -q '.content' | base64 -d | sed -n '1,200p' || true
else
  curl -sSL 'https://raw.githubusercontent.com/github/docs-content/main/content/rest/issues/assignees.md' | sed -n '1,200p' || true
fi

echo "== Behavioral model of assignee_count/add logic from source =="
python3 - <<'PY'
from pathlib import Path
p=Path('.github/scripts/assign-issue.sh')
if p.exists():
    s=p.read_text()
    for needle in ['assignee_count','assignees | length','add-assignee','assignee_assigned']:
        print(needle, 'present' if needle in s else 'absent')
else:
    print('file_missing')
PY

Repository: vybestack/llxprt-code

Length of output: 268


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== File excerpt =="
awk '{ printf "%6d\t%s\n", NR, $0 }' .github/scripts/assign-issue.sh | sed -n '1,160p'

echo "== Relevant symbols present =="
python3 - <<'PY'
from pathlib import Path
p = Path('.github/scripts/assign-issue.sh')
if not p.exists():
    print('file_missing')
else:
    s = p.read_text()
    for needle in ['assignee_count', '.assignees | length', 'add-assignee', 'assignee_assigned']:
        print(f'{needle!r}:', 'present' if needle in s else 'absent')
PY

echo "== Read-only behavioral parser/snippet model =="
python3 - <<'PY'
from pathlib import Path
import re
p = Path('.github/scripts/assign-issue.sh')
s = p.read_text().splitlines()
for i, line in enumerate(s, start=1):
    if 'assignee_count' in line or 'assignees | length' in line or '--add-assignee' in line or '--add-assignees' in line or 'assignee_assigned' in line:
        # print up to 3 lines around each match
        lo=max(1,i-3); hi=len(s)+1 if i==len(s) else i+4
        print(f"\n--- occurrences near line {i} ---")
        for n in range(lo,hi):
            if 1<=n<=len(s):
                print(f"{n}: {s[n-1]}")
PY

echo "== Check for add-assignee/add-assignees and failure-handling guard pattern =="
python3 - <<'PY'
from pathlib import Path
import re
p = Path('.github/scripts/assign-issue.sh')
lines = [l.strip() for l in p.read_text().splitlines()]
print("gh assign calls:")
for line in lines:
    if 'gh ' in line and ('add-assignee' in line or 'add-assignees' in line):
        print(line)
print("assignee assignment condition lines:")
for line in lines:
    if 'add-assignee' in line or 'add-assignees' in line or 'assignee_assigned' in line:
        print(line)
PY

Repository: vybestack/llxprt-code

Length of output: 8758


🌐 Web query:

GitHub REST API Add assignees to an issue updates existing assignees not replace

💡 Result:

The GitHub REST API provides two distinct ways to manage issue assignees, depending on whether you wish to add users or replace the entire list [1][2]. To add assignees without removing existing ones, use the Add assignees to an issue endpoint [1][3]: POST /repos/{owner}/{repo}/issues/{issue_number}/assignees According to official GitHub documentation, this endpoint adds the specified users to the issue while ensuring that users already assigned are not replaced [1][4]. You can add up to 10 assignees in a single request by providing an array of usernames in the body [1][3]. In contrast, the Update an issue endpoint (PATCH /repos/{owner}/{repo}/issues/{issue_number}) behaves differently [2]. When you provide an assignees array via the PATCH method, it replaces the entire set of current assignees with the new list provided in the request [2]. Passing an empty array ([]) to the PATCH endpoint will clear all assignees from the issue [2]. Only users with push access to the repository have the authority to modify issue assignees [1][2]. If a user without sufficient permissions attempts these changes, the assignee modifications are silently ignored [1][2].

Citations:


🌐 Web query:

GitHub CLI issue edit --add-assignee REST Add Assignees PATCH issue body assignees replaces behavior

💡 Result:

The behavior of assignee management in the GitHub CLI (gh) and the underlying GitHub REST API differs fundamentally: GitHub CLI (gh issue edit) The gh issue edit command does not perform a direct replacement of assignees when using flags. Instead, it provides specific flags to manage the assignee list incrementally [1][2]: --add-assignee: Adds the specified user(s) to the existing list of assignees [1][3]. --remove-assignee: Removes the specified user(s) from the existing list of assignees [1][3]. This design was explicitly implemented to avoid accidental overwrites and to allow piecemeal updates to issue metadata [2]. You can combine these flags in a single command (e.g., gh issue edit 23 --add-assignee "@me" --remove-assignee monalisa) to achieve the effect of a reassign or update [1][3]. GitHub REST API (PATCH /repos/{owner}/{repo}/issues/{issue_number}) In contrast, the GitHub REST API's PATCH endpoint for updating an issue behaves as a replacement operation [4][5]. When you provide the assignees array in the request body, it replaces the entire set of current assignees with the new list provided [4][5]. Sending an empty array ([]) to this endpoint will clear all assignees from the issue [4][5]. For targeted additions or removals via the REST API without replacing the entire list, you should use the dedicated Assignees endpoints [6][7]: POST /repos/{owner}/{repo}/issues/{issue_number}/assignees: Adds assignees without removing existing ones [6][7]. DELETE /repos/{owner}/{repo}/issues/{issue_number}/assignees: Removes specific assignees from the issue [6][7].

Citations:


Fail closed when reading current assignees.

|| echo "0" makes API failures indistinguishable from an unassigned issue. That allows the assign guard to be skipped and gh issue edit --add-assignee ... to proceed; gh issue edit --add-assignee is additive and does not replace existing assignees, so a failed read can create a co-assignment.

Abort and surface feedback when the assignee read fails; only continue when the assignee_count read succeeds.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/assign-issue.sh around lines 60 - 63, Update the assignee
count read in the assign guard to fail closed: remove the fallback that converts
gh issue view errors into 0, capture or propagate the command failure, and abort
with clear feedback when reading current assignees fails. Continue to the
assignment flow only when assignee_count is successfully obtained.

Comment on lines +60 to +68
assignee_count="$(
gh issue view "${ISSUE}" --repo "${REPO}" --json assignees \
--jq '.assignees | length' 2>/dev/null || echo "0"
)"

if [[ "${assignee_count}" -gt 0 ]]; then
current="$(
gh issue view "${ISSUE}" --repo "${REPO}" --json assignees \
--jq '[.assignees[].login] | join(", ")' 2>/dev/null || echo "someone"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | rg '(^|/)(assign-issue\.sh|.*workflow.*\.ya?ml|.*workflow.*\.ya?ml)$' || true

echo
echo "Target file outline/lines:"
if [ -f .github/scripts/assign-issue.sh ]; then
  wc -l .github/scripts/assign-issue.sh
  sed -n '1,190p' .github/scripts/assign-issue.sh | nl -ba
else
  fd -a 'assign-issue\.sh$' .github || true
fi

echo
echo "Workflow references to assign issue:"
rg -n "assign-issue|assign|add-assignee|concurrency|issues: create|issue_comment" .github -S || true

Repository: vybestack/llxprt-code

Length of output: 894


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "assign-issue.sh:"
cat -n .github/scripts/assign-issue.sh

echo
echo "All workflow trigger/concurrency/assignment references:"
for f in .github/workflows/*.yml .github/workflows/*.yaml; do
  [ -f "$f" ] || continue
  echo "--- $f"
  rg -n "on:|issue|comment|assign|concurrency|runs-on|uses:" "$f" -S || true
done

Repository: vybestack/llxprt-code

Length of output: 50377


Serialize /assign checks with the assignment mutation.

.github/workflows/assign.yml uses a concurrency group that includes the commenter login, so different commenters on the same issue can both pass the zero-assignee check and --add-assignee can add both users. Move the queue to a per-issue group with cancel-in-progress: false, or re-fetch after assignment and reject/roll back the actor when another assignee was added.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/assign-issue.sh around lines 60 - 68, Serialize the entire
assignment check-and-mutation flow in the `/assign` workflow per issue, rather
than per commenter, by updating the concurrency group in the workflow
configuration and setting cancel-in-progress to false. Ensure the existing
zero-assignee check and add-assignee operation in the assignment script execute
within that shared per-issue queue.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

github action of /assign

2 participants