Skip to content

refactor: new issue take-untake workflow - #200

Merged
Ryan-Millard merged 20 commits into
Ryan-Millard:mainfrom
laxitajain:fix/issue-claim-workflow
Jan 8, 2026
Merged

refactor: new issue take-untake workflow#200
Ryan-Millard merged 20 commits into
Ryan-Millard:mainfrom
laxitajain:fix/issue-claim-workflow

Conversation

@laxitajain

@laxitajain laxitajain commented Jan 5, 2026

Copy link
Copy Markdown
Contributor

⚠️ Are you using the correct pull request template?
Please choose one of the following:

If none of these fit, you may use this default to describe your change manually.

If this is the right template, go ahead and complete it below 👇


📌 Description

fixes #129

✅ Type of Change

Place an "x" in the brackets below:

  • Bug fix 🐛
  • New feature ✨
  • Refactor 🔧
  • Documentation 📚
  • Build/dependency update 🧱
  • Other (describe):

🧪 How Has This Been Tested?

Please describe how you tested your changes (e.g., unit tests, manual testing, screenshots, etc.)

🧩 Checklist

Place an "x" in the brackets below:

  • I’ve followed the contribution guidelines.
  • My code follows the code style of this project.
  • I’ve added tests where necessary.
  • I’ve updated the documentation where applicable.
  • I’ve linked related issues or discussions (if any).
  • I’ve checked for breaking changes and backwards compatibility.

📸 Screenshots / Demo (if applicable)

Paste images, GIFs, or demo links here.

💬 Additional Context

Anything else relevant to the PR.

Summary by CodeRabbit

  • Documentation

    • Added "Claiming Issues" guide: /take, /untake (including claiming/releasing for others), permission rules, visible claim banners, and 21-day claim expiry.
  • Chores

    • Consolidated claim handling into one workflow using a hidden, single-source claim state embedded in the issue body; marker-driven banners, permission-checked take/untake (self or on-behalf), in-place claim persistence, and weekly expiry cleanup.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 5, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Consolidates issue-claim handling into a single take_untake GitHub Actions job: embeds structured meta in a single bot comment, renders a visible issue banner, enforces 21‑day expirations (event-driven and weekly), adds permission checks for /take and /untake, and removes the instructional bot comment on new issues. (41 words)

Changes

Cohort / File(s) Summary
Workflow Consolidation
\.github/workflows/issue-take-untake.yml
Merges jobs into one take_untake workflow triggered on issues, issue_comment, and schedule; introduces META_START/META_END + BODY_START/BODY_END markers, single-comment meta persistence, unified /take and /untake handling with permission checks, expiry normalization (EXPIRE_MS = 21 days), weekly cleanup, and removes the instructional bot comment on new issues.
Docs: Claiming guidance
docs/docs/guidelines/CONTRIBUTING.md
Adds "Claiming Issues" section describing the hidden meta block as single source of truth, visible claim banner, /take and /untake command semantics (including /take @user``), permission rules, and 21‑day claim expiry/cleanup behavior.
Contributor docs update
CONTRIBUTING.md
Adds Docker commands under Code Quality, updates link/formatting, and revises the "Questions?" guidance text.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant GitHub as GitHub Events
    participant Workflow as take_untake Job
    participant API as GitHub API

    Note over User,GitHub: User opens issue or posts comment with /take or /untake
    User->>GitHub: Open issue / Post comment
    GitHub->>Workflow: Trigger (issues / issue_comment / schedule)
    Workflow->>Workflow: Parse event, extract command, read single meta-comment (META_START/META_END)
    alt /take
        Workflow->>API: Verify permissions, add `taken` label if needed
        Workflow->>API: Update issue body banner (BODY_START/BODY_END)
        Workflow->>API: Create/update single meta-comment with owner and timestamp
    else /untake or expiry
        Workflow->>API: Verify permissions, remove `taken` label
        Workflow->>API: Update issue body to untaken banner
        Workflow->>API: Update single meta-comment to clear owner
    end
    API-->>GitHub: Labels/body/comment updated
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Poem

🐰 I tuck state between META marks so neat,

One comment holds each claim and every beat.
Take, untake — twenty-one days then roam,
Labels hop off and banners come home.
A tidy burrow for each issue seat.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: refactoring the issue take-untake workflow with a new implementation approach.
Linked Issues check ✅ Passed All coding requirements from issue #129 are met: removed bot instruction posts, preserved /take and /untake commands with permission checks, centralized state in issue body metadata, and documented in CONTRIBUTING.
Out of Scope Changes check ✅ Passed Minor documentation updates to CONTRIBUTING.md for Docker commands and style are slightly tangential but reasonably scoped as supporting changes to the main workflow refactor.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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


📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 554601c and f8ab90b.

📒 Files selected for processing (1)
  • CONTRIBUTING.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • CONTRIBUTING.md

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.

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

Fix all issues with AI Agents 🤖
In @.github/workflows/issue-take-untake.yml:
- Around line 299-301: The empty catch block (catch {}) around the expiration
logic silently swallows all errors; replace it with a catch that captures the
error (e.g., catch (err) { ... }) and log the error for observability—include
contextual info such as the issue identifier and action being performed (for
example: console.error or core.error with a message like "Error processing
expiration for issue <issueNumber>:" and the error object) while otherwise
preserving the existing expiration logic and control flow.
- Around line 277-300: The cleanup branch that handles expired meta (the block
checking if meta.ts && Date.now() - meta.ts > EXPIRE_MS) removes the label and
updates the issue body but does not remove the old meta comment; update it to
mirror persistMeta by calling github.rest.issues.deleteComment using the stored
meta comment id (e.g., meta.commentId or meta.id depending on the meta shape)
before updating the body, guard the delete call if the id is missing, and keep
the existing removeLabel and update calls and error handling so orphaned meta
comments are cleaned up when a claim expires.

Caution

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

⚠️ Outside diff range comments (1)
.github/workflows/issue-take-untake.yml (1)

1-1: Fix Prettier formatting to resolve CI failure.

The pipeline indicates a Prettier formatting check failure. Run prettier --write on this file to fix code style issues.

🧹 Nitpick comments (6)
docs/docs/guidelines/CONTRIBUTING.md (1)

26-31: Documentation looks good but consider mentioning claim expiration.

The new section clearly explains the /take and /untake commands. However, the workflow implements a 21-day claim expiration that isn't documented here. Contributors may be surprised when their claims expire automatically.

Also, line 29 is missing a trailing period for consistency with line 28.

🔎 Suggested improvements
 ## Claiming Issues
 
 - To claim an issue, comment: `/take`. This will assign the issue to you and add the `taken` label.
-- To release an issue, comment: `/untake`
+- To release an issue, comment: `/untake`.
+- Claims expire automatically after 21 days of inactivity.
 - Issues labeled `taken` are currently owned and being worked on.
.github/workflows/issue-take-untake.yml (5)

88-101: Silent error swallowing on label creation.

The empty catch block at line 97 silently ignores all errors. While label creation failing because the label exists is expected, other errors (network issues, permissions) would also be silently ignored.

🔎 Suggested improvement
             try {
               await github.rest.issues.createLabel({
                 owner,
                 repo,
                 name: takenLabel,
                 color: 'ff0000',
                 description: 'Issue is currently claimed'
               });
-            } catch (e) {}
+            } catch (e) {
+              // Label already exists - this is expected
+              if (e.status !== 422) {
+                console.warn('Failed to create label:', e.message);
+              }
+            }

141-163: Inefficient re-fetch and inconsistent per_page values.

persistMeta re-fetches comments (line 143) even though they were just fetched at lines 113-118. This doubles API calls. Additionally, per_page: 100 here differs from per_page: 200 used earlier, which could cause inconsistencies on issues with many comments.

Consider passing the existing comments array as a parameter or standardizing per_page.

🔎 Suggested improvement
-          async function persistMeta(metaObj, claimed) {
-            // Find existing meta comments
-            const commentsResp = await github.rest.issues.listComments({
-              owner,
-              repo,
-              issue_number: issueNumber,
-              per_page: 100
-            });
-
-            const metaComments = commentsResp.data.filter(
-              c => c.user?.login === BOT_LOGIN && c.body?.includes(MARKER_PREFIX)
-            );
+          async function persistMeta(metaObj, claimed, existingComments) {
+            const metaComments = existingComments.filter(
+              c => c.user?.login === BOT_LOGIN && c.body?.includes(MARKER_PREFIX)
+            );

Then call it with the already-fetched comments:

await persistMeta(meta, true, commentsResp.data);

225-241: Duplicated constants and buildUntakenBody function.

The constants (MARKER_PREFIX, BODY_START, BODY_END, EXPIRE_MS) and buildUntakenBody() are duplicated between the take_untake and cleanup jobs. This violates DRY and risks drift if one is updated but not the other.

Consider extracting shared logic to a reusable action or at minimum adding a comment noting the duplication.


200-211: No user feedback for command success or failure.

When /take or /untake commands succeed, the user receives no confirmation comment. When commands fail (e.g., /take on an already-claimed issue, or /untake by a non-owner), the workflow silently does nothing. Users must infer success from label/body changes.

Consider adding brief confirmation or error comments for better UX.


120-120: Unused members field in meta object.

The members: [] field is initialized but never read or modified. If this is placeholder for future functionality, consider adding a comment. Otherwise, remove it to reduce confusion.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e63ac9 and 385054f.

📒 Files selected for processing (2)
  • .github/workflows/issue-take-untake.yml
  • docs/docs/guidelines/CONTRIBUTING.md
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-04T15:34:04.654Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 146
File: docs/docs/reference/react/components/ThemeSwitch/tests.md:123-124
Timestamp: 2026-01-04T15:34:04.654Z
Learning: In Docusaurus documentation, relative directory links (e.g., ../ or ../../../hooks/useTheme) resolve to index.md within the target directory. Do not require or force linking to index.md explicitly; such relative links remain valid and should not be changed when reviewing docs under the docs directory.

Applied to files:

  • docs/docs/guidelines/CONTRIBUTING.md
🪛 GitHub Actions: CI
.github/workflows/issue-take-untake.yml

[error] 1-1: Prettier formatting check failed. Run 'prettier --write' to fix code style issues in this file.

🔇 Additional comments (1)
.github/workflows/issue-take-untake.yml (1)

16-21: Job condition logic is correct.

The condition correctly ensures the job runs for issue events and for issue comments not made by the bot itself, preventing infinite loops.

Comment thread .github/workflows/issue-take-untake.yml Outdated
Comment thread .github/workflows/issue-take-untake.yml Outdated

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

Fix all issues with AI Agents 🤖
In @.github/workflows/issue-take-untake.yml:
- Around line 200-211: Add explicit user feedback when commands fail: when
isTake is true but meta.owner is already set, post a comment like "This issue is
already claimed by @<owner>" instead of silently returning; when isUntake is
true but meta.owner !== commenter, post a comment like "You can't untake this
issue because you haven't claimed it" before returning. Use the workflow's
existing issue-commenting helper (the same mechanism used elsewhere to post
comments) to create these messages, and keep using persistMeta(meta, true/false)
only for successful take/untake paths.
- Around line 113-118: The call to github.rest.issues.listComments uses
per_page: 200 which exceeds GitHub's 100-item maximum and can miss comments;
update the call to use per_page: 100 or replace it with proper pagination (e.g.,
use github.paginate or loop with page and per_page parameters) so you fetch all
comments for issueNumber; locate the listComments invocation (variable
commentsResp) and either set per_page: 100 or implement pagination to aggregate
all pages before searching for the meta comment.
♻️ Duplicate comments (2)
.github/workflows/issue-take-untake.yml (2)

277-298: Cleanup job doesn't delete expired meta comments.

After expiring a claim, the cleanup job removes the label and updates the body but doesn't delete the old meta comment. In contrast, persistMeta in the take_untake job deletes old meta comments before creating new ones (lines 154-163). This inconsistency leaves orphaned meta comments that could cause unexpected behavior if the issue is re-claimed.

🔎 Suggested fix
               if (meta.ts && Date.now() - meta.ts > EXPIRE_MS) {
+                // Delete the expired meta comment
+                try {
+                  await github.rest.issues.deleteComment({
+                    owner,
+                    repo,
+                    comment_id: metaComment.id
+                  });
+                } catch {}
+
                 await github.rest.issues.removeLabel({
                   owner,
                   repo,
                   issue_number: issue.number,
                   name: takenLabel
                 });

Based on learnings, this issue was previously identified but remains unresolved.


300-300: Overly broad error suppression hides failures.

The empty catch {} wraps the entire expiration logic for each issue. This silently suppresses all errors including API failures, network issues, and permission problems, making debugging difficult.

At minimum, log the error for observability.

🔎 Suggested fix
-              } catch {}
+              } catch (e) {
+                console.error(`Failed to expire claim on issue #${issue.number}:`, e.message);
+              }

Based on learnings, this issue was previously identified but remains unresolved.

🧹 Nitpick comments (5)
.github/workflows/issue-take-untake.yml (5)

17-20: Consider applying bot filter to all event types for consistency.

The condition github.actor != 'github-actions[bot]' only applies to issue_comment events. While unlikely, if the bot programmatically opens issues, the workflow might process its own issue events. For defensive consistency, consider checking the actor for all event types.

🔎 Optional refactor
-  if: >
-    github.event_name == 'issues' ||
-    (github.event_name == 'issue_comment' &&
-    github.actor != 'github-actions[bot]')
+  if: >
+    github.actor != 'github-actions[bot]' && (
+      github.event_name == 'issues' ||
+      github.event_name == 'issue_comment'
+    )

143-148: Code duplication: Comment fetching logic repeated.

The same comment fetching and filtering logic appears at lines 113-118 and 143-148. This duplication increases maintenance burden and introduces inconsistency (different per_page values: 200 vs 100).

Consider extracting a shared helper function to fetch meta comments.

🔎 Suggested refactor
+            async function getMetaComments() {
+              const commentsResp = await github.rest.issues.listComments({
+                owner,
+                repo,
+                issue_number: issueNumber,
+                per_page: 100 // Use consistent value
+              });
+              return commentsResp.data.filter(
+                c => c.user?.login === BOT_LOGIN && c.body?.includes(MARKER_PREFIX)
+              );
+            }
+
             // Then at line 113, replace with:
-            const commentsResp = await github.rest.issues.listComments({
-              owner,
-              repo,
-              issue_number: issueNumber,
-              per_page: 200
-            });
-
             let meta = { owner: null, members: [], ts: 0 };
-            const latestMeta = commentsResp.data
-              .filter(c => c.user?.login === BOT_LOGIN && c.body?.includes(MARKER_PREFIX))
+            const metaComments = await getMetaComments();
+            const latestMeta = metaComments
               .sort((a, b) => new Date(a.created_at) - new Date(b.created_at))
               .pop();
             
             // And at line 143, replace with:
-            const commentsResp = await github.rest.issues.listComments({
-              owner,
-              repo,
-              issue_number: issueNumber,
-              per_page: 100
-            });
-
-            const metaComments = commentsResp.data.filter(
-              c => c.user?.login === BOT_LOGIN && c.body?.includes(MARKER_PREFIX)
-            );
+            const metaComments = await getMetaComments();

194-197: Expired claims remain visible until next interaction.

While this normalization prevents expired owners from blocking new claims, it doesn't update the issue state (label/body/meta comment). Users viewing the issue will still see it as claimed until someone runs /take or the scheduled cleanup runs.

Consider proactively calling persistMeta(meta, false) when an expired claim is detected to immediately reflect the unclaimed state.

🔎 Optional enhancement
 // Normalize expired claims before handling commands
 if (meta.owner && meta.ts && Date.now() - meta.ts > EXPIRE_MS) {
   meta = { owner: null, members: [], ts: 0 };
+  // Proactively clean up the expired claim
+  await persistMeta(meta, false);
+  return;
 }

230-241: Code duplication: buildUntakenBody function repeated across jobs.

The buildUntakenBody function is duplicated between the take_untake job (lines 43-54) and the cleanup job (lines 230-241). This violates DRY principles and creates maintenance burden—any changes to the banner format must be applied in two places.

Unfortunately, GitHub Actions doesn't support sharing functions between jobs in the same workflow file. Consider one of these approaches:

  1. Extract to a separate action (composite or JavaScript action)
  2. Use a reusable workflow with outputs
  3. Document the duplication with a comment noting they must stay in sync

224-229: Code duplication: Constants repeated across jobs.

Constants like takenLabel, MARKER_PREFIX, BODY_START, BODY_END, and EXPIRE_MS are defined in both the take_untake job (lines 31-36) and the cleanup job (lines 224-229). If these values need to change, both locations must be updated.

While GitHub Actions doesn't provide an easy way to share constants between jobs within a single workflow, consider:

  1. Using repository variables or environment variables at the workflow level
  2. Documenting the duplication with comments noting they must stay synchronized
🔎 Optional enhancement using workflow-level env
 name: Issue Take/Untake Workflow

+env:
+  TAKEN_LABEL: 'taken'
+  MARKER_PREFIX: '<!-- take-meta:'
+  BODY_START: '<!-- issue-take-untake:start -->'
+  BODY_END: '<!-- issue-take-untake:end -->'
+  EXPIRE_DAYS: '21'
+
 on:
   issues:
     types: [opened]

Then reference with process.env.TAKEN_LABEL in the scripts. Note: This requires string parsing for numeric values.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 385054f and 2ad3ae9.

📒 Files selected for processing (1)
  • .github/workflows/issue-take-untake.yml
🔇 Additional comments (2)
.github/workflows/issue-take-untake.yml (2)

88-101: LGTM - meets PR objectives.

The issue open handler correctly initializes new issues with an untaken body banner instead of posting bot comments, aligning with the goal of reducing noise. The label creation with error suppression is appropriate since the label may already exist.


49-49: Verify that GitHub issue #99 exists and contains the claim system documentation.

The workflow references issue #99 for claim system information, but this should be verified to ensure the link is valid. While comprehensive documentation of the /take and /untake workflow exists locally in docs/docs/guidelines/CONTRIBUTING.md, it would be worth confirming that issue #99 is properly set up as an external reference point for users seeking additional context.

Comment thread .github/workflows/issue-take-untake.yml Outdated
Comment thread .github/workflows/issue-take-untake.yml Outdated

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

♻️ Duplicate comments (2)
.github/workflows/issue-take-untake.yml (2)

200-211: Missing user feedback for failed commands remains unaddressed.

When /take fails (issue already claimed) or /untake fails (commenter is not the owner), the workflow silently does nothing. Users receive no indication why their command was ignored. This was flagged in a previous review and appears to still be unaddressed.


267-310: Overly broad error suppression still present.

The catch {} at line 310 wraps the entire expiration logic for each issue, silently suppressing all errors including API failures, network issues, and permission problems. This was flagged in a previous review and remains unaddressed. At minimum, log the error for observability.

🔎 Suggested fix (from previous review)
-              } catch {}
+              } catch (e) {
+                console.error(`Failed to expire claim on issue #${issue.number}:`, e.message);
+              }
🧹 Nitpick comments (5)
.github/workflows/issue-take-untake.yml (5)

88-101: Consider documenting expected error in the empty catch block.

The empty catch (e) {} at line 97 is intentional (label may already exist), but it's a code smell that could hide unexpected errors. Consider either checking for the specific error code (422 for already exists) or adding a comment explaining the intent.

🔎 Suggested improvement
             try {
               await github.rest.issues.createLabel({
                 owner,
                 repo,
                 name: takenLabel,
                 color: 'ff0000',
                 description: 'Issue is currently claimed'
               });
-            } catch (e) {}
+            } catch (e) {
+              // 422 expected if label already exists - ignore
+            }

113-139: Pagination not implemented for issues with >100 comments.

While per_page: 100 is the correct API maximum, issues with more than 100 comments may have their meta comment missed if it falls outside the first page. Consider using github.paginate() for complete coverage, or document this as a known limitation.

🔎 Suggested improvement using pagination
-            const commentsResp = await github.rest.issues.listComments({
-              owner,
-              repo,
-              issue_number: issueNumber,
-              per_page: 100
-            });
+            const allComments = await github.paginate(
+              github.rest.issues.listComments,
+              { owner, repo, issue_number: issueNumber, per_page: 100 }
+            );
 
             let meta = { owner: null, members: [], ts: 0 };
-            const latestMeta = commentsResp.data
+            const latestMeta = allComments
               .filter(c => c.user?.login === BOT_LOGIN && c.body?.includes(MARKER_PREFIX))
               .sort((a, b) => new Date(a.created_at) - new Date(b.created_at))
               .pop();

141-192: Consider logging errors in catch blocks for observability.

The persistMeta function has empty catch blocks at lines 162 and 189. While ignoring errors during cleanup operations can be acceptable, silent failures make debugging difficult. Consider logging errors for observability, especially for the delete operation at line 162 which could indicate permission issues.

🔎 Suggested improvement
               try {
                 await github.rest.issues.deleteComment({
                   owner,
                   repo,
                   comment_id: c.id
                 });
-              } catch {}
+              } catch (e) {
+                console.warn(`Failed to delete meta comment ${c.id}: ${e.message}`);
+              }

213-241: Code duplication between jobs.

The buildUntakenBody function and several constants (MARKER_PREFIX, BODY_START, BODY_END, EXPIRE_MS) are duplicated between the take_untake and cleanup jobs. Consider extracting these to a shared location (e.g., a reusable workflow, composite action, or shared JavaScript file) to reduce maintenance burden and ensure consistency.


243-249: Cleanup job lacks pagination for issues list.

If the repository has more than 100 open issues with the taken label, only the first page will be processed, potentially leaving stale claims unexpired indefinitely. Consider using github.paginate() to process all matching issues.

🔎 Suggested fix
-            const issues = await github.rest.issues.listForRepo({
+            const issues = await github.paginate(
+              github.rest.issues.listForRepo,
+              {
               owner,
               repo,
               labels: takenLabel,
               state: 'open',
               per_page: 100
-            });
+              }
+            );
 
-            for (const issue of issues.data) {
+            for (const issue of issues) {
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2ad3ae9 and 6dea0c0.

📒 Files selected for processing (1)
  • .github/workflows/issue-take-untake.yml
🔇 Additional comments (5)
.github/workflows/issue-take-untake.yml (5)

1-14: LGTM!

The workflow triggers and permissions are appropriately configured. The permissions follow the principle of least privilege with only contents: read and issues: write as needed.


16-21: LGTM!

The job condition correctly filters events: it runs on issue opening or on comments not made by the bot itself, preventing infinite loops.


28-70: LGTM!

Well-structured constants and helper functions. The body templates use GitHub-flavored markdown alerts appropriately and provide clear user guidance with links to documentation.


72-86: LGTM!

The updateIssueBody function correctly handles both cases: replacing an existing banner or prepending one to the issue body. The regex pattern works correctly for matching content between the HTML comment markers.


103-111: LGTM!

The command detection logic is well-implemented with case-insensitive regex patterns that correctly match /take and /untake at the beginning of a comment, with or without trailing content.

@Ryan-Millard Ryan-Millard left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Hi @laxitajain, thank you for the great work so far on this refactor!

Before this can be merged, I’d like to request a few changes and clarifications around how issue metadata and claim handling works:

  1. Metadata Storage
    Right now the workflow embeds state metadata as bot comments and parses it from there. This leads to a lot of noisy comment creation/deletion and makes it hard to manage reliably - maintainers will also get a lot of emails as a result of these comments. Instead of storing internal state in comments, please revise the implementation so that metadata is stored in a more stable place — for example:

    • inside the issue body, in a dedicated section or hidden block
    • or encoded in another structured field
      Storing state in the issue body or a dedicated metadata field avoids unnecessary GitHub notifications and makes the workflow easier to reason about.
  2. Permissions: Triage and Maintainers
    The current logic locks a claim strictly to the person who ran /take. We also need support for users with triage or higher permissions to:

    • remove someone else’s claim (e.g., if the person is unavailable or the claim is incorrect)
    • claim on behalf of someone else when appropriate (e.g., because of a bug or task reassignment).
      Please update the claim handling logic to respect permission levels accordingly.
  3. Documentation Update
    Ensure that the updated metadata storage approach and permission behavior are clearly documented in the CONTRIBUTING guideline docs so users know exactly where state is stored and how permissions affect claims.

Once these adjustments are made, this will be in good shape to merge 🙂

@laxitajain

Copy link
Copy Markdown
Contributor Author

Hi @laxitajain, thank you for the great work so far on this refactor!

Before this can be merged, I’d like to request a few changes and clarifications around how issue metadata and claim handling works:

  1. Metadata Storage
    Right now the workflow embeds state metadata as bot comments and parses it from there. This leads to a lot of noisy comment creation/deletion and makes it hard to manage reliably - maintainers will also get a lot of emails as a result of these comments. Instead of storing internal state in comments, please revise the implementation so that metadata is stored in a more stable place — for example:

    • inside the issue body, in a dedicated section or hidden block
    • or encoded in another structured field
      Storing state in the issue body or a dedicated metadata field avoids unnecessary GitHub notifications and makes the workflow easier to reason about.
  2. Permissions: Triage and Maintainers
    The current logic locks a claim strictly to the person who ran /take. We also need support for users with triage or higher permissions to:

    • remove someone else’s claim (e.g., if the person is unavailable or the claim is incorrect)
    • claim on behalf of someone else when appropriate (e.g., because of a bug or task reassignment).
      Please update the claim handling logic to respect permission levels accordingly.
  3. Documentation Update
    Ensure that the updated metadata storage approach and permission behavior are clearly documented in the CONTRIBUTING guideline docs so users know exactly where state is stored and how permissions affect claims.

Once these adjustments are made, this will be in good shape to merge 🙂

Hey, thank you for the detailed feedback, i'll be continuing work on this PR today, was caught up with some things.

@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

🤖 Fix all issues with AI agents
In @.github/workflows/issue-take-untake.yml:
- Around line 74-84: In buildTakenBanner(user) the template references an
undefined variable ownerName; replace that reference with the provided user
parameter so the banner uses **@${user}** for the revoke message; update the
template inside buildTakenBanner to use user (the function parameter) wherever
ownerName was used and ensure trimming/formatting remains unchanged.

In @docs/docs/guidelines/CONTRIBUTING.md:
- Line 28: The "Commands" heading is currently at level 4 (####) and should be
level 3 to follow the document hierarchy; update the heading token for the
"Commands" line from "#### Commands" to "### Commands" so it sits under the
preceding h2 and maintains correct markdown structure.
🧹 Nitpick comments (1)
.github/workflows/issue-take-untake.yml (1)

169-172: Consider extracting shared constants to job environment variables.

The constants EXPIRE_MS, META_START, and META_END are duplicated between the take_untake and cleanup jobs. While inline script duplication is acceptable, you could reduce maintenance burden by defining these as environment variables at the workflow level if they change frequently.

💡 Example approach

At the workflow level:

env:
  EXPIRE_MS: 1814400000  # 21 * 24 * 60 * 60 * 1000
  META_START: '<!-- issue-take-untake:meta'
  META_END: '-->'
  BODY_START: '<!-- issue-take-untake:start -->'
  BODY_END: '<!-- issue-take-untake:end -->'

Then reference in scripts via process.env.EXPIRE_MS, etc. However, this is purely optional given the simplicity of the current approach.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6dea0c0 and 924da8c.

📒 Files selected for processing (2)
  • .github/workflows/issue-take-untake.yml
  • docs/docs/guidelines/CONTRIBUTING.md
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-04T15:34:04.654Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 146
File: docs/docs/reference/react/components/ThemeSwitch/tests.md:123-124
Timestamp: 2026-01-04T15:34:04.654Z
Learning: In Docusaurus documentation, relative directory links (e.g., ../ or ../../../hooks/useTheme) resolve to index.md within the target directory. Do not require or force linking to index.md explicitly; such relative links remain valid and should not be changed when reviewing docs under the docs directory.

Applied to files:

  • docs/docs/guidelines/CONTRIBUTING.md
🪛 markdownlint-cli2 (0.18.1)
docs/docs/guidelines/CONTRIBUTING.md

28-28: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)

🔇 Additional comments (13)
docs/docs/guidelines/CONTRIBUTING.md (1)

26-37: LGTM! Clear documentation of the claiming workflow.

The section clearly documents the /take and /untake commands, permission requirements for acting on behalf of others, and the 21-day expiry. This addresses the PR objective to centralize guidance in the contribution guide.

.github/workflows/issue-take-untake.yml (12)

16-21: LGTM! Job condition correctly filters events.

The condition appropriately runs the job for issue opens and non-bot issue comments, preventing the bot from reacting to its own comments.


29-37: LGTM! Constants are well-defined.

The 21-day expiration matches the documentation, and the HTML comment markers provide a hidden metadata storage mechanism in the issue body as requested by the reviewer.


45-55: LGTM! Robust metadata extraction.

The function safely extracts metadata with appropriate error handling, returning a sensible default when metadata is missing or malformed.


57-62: LGTM! Metadata persistence logic is sound.

The function correctly updates existing metadata or prepends it when absent, ensuring state persistence in the issue body.


64-72: LGTM! Clear untaken banner.

The banner provides helpful guidance to contributors and uses appropriate GitHub alert syntax.


86-90: LGTM! Banner update logic is correct.

The function properly replaces existing banners or prepends new ones with a visual separator.


92-99: LGTM! Permission check implementation is correct.

The function properly retrieves user permission levels from the GitHub API.


101-106: LGTM! Expired claim normalization.

The logic correctly resets expired claims before processing new commands, preventing stale state from affecting operations.


108-114: LGTM! Issue initialization logic is correct.

The logic properly initializes new issues with untaken state metadata and banner, addressing the PR objective to remove instructional bot comments while preserving claim functionality.


116-130: LGTM! Comment parsing and permission logic is well-structured.

The logic correctly parses /take and /untake commands with optional user mentions, and properly determines privilege levels for triage+ users as requested by the reviewer.


132-143: LGTM! Take logic correctly implements permission-based claiming.

The logic properly enforces single ownership, allows triage+ users to claim on behalf of others (addressing reviewer requirements), and atomically updates labels and body.


146-158: LGTM! Untake logic correctly implements permission-based release.

The logic properly enforces that only the claim owner or triage+ users can release a claim (addressing reviewer requirements), and handles missing labels gracefully.

Comment thread .github/workflows/issue-take-untake.yml
Comment thread .github/workflows/issue-take-untake.yml Outdated
Comment thread docs/docs/guidelines/CONTRIBUTING.md Outdated
laxitajain and others added 3 commits January 8, 2026 18:32
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

@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

🤖 Fix all issues with AI agents
In @.github/workflows/issue-take-untake.yml:
- Around line 90-97: The getPermission function currently calls
github.rest.repos.getCollaboratorPermissionLevel which throws a 404 for
non-collaborators; wrap that call in a try/catch inside getPermission, catch
errors where error.status === 404 (or error.response?.status === 404) and return
a safe default like 'none' (or null) for non-collaborators, rethrow or propagate
other unexpected errors, and ensure downstream logic treats the default as "no
permission" so /take doesn't fail for first-time external contributors.
- Around line 120-121: The username capture in the takeMatch and untakeMatch
regexes uses \w+ which excludes hyphens; update both patterns to allow hyphens
in GitHub usernames by replacing (\w+) with ([\w-]+) (or an explicit class like
[A-Za-z0-9-]+) so commands like "/take @Ryan-Millard" and "/untake
@Ryan-Millard" correctly capture the username.
- Around line 143-156: Replace the current permission check that compares
meta.owner to targetUser with a clear rule: privileged users (isPrivileged) can
always untake, and non-privileged users may only untake if they are the actual
claim owner; specifically, in the UNTAKE branch (untakeMatch) change the guard
to return unless isPrivileged or meta.owner === commenter (or the variable
representing the comment author), so the targetUser argument no longer
grants/blocks permission for non-privileged users.
- Around line 184-190: The current call to github.rest.issues.listForRepo
(assigning to issues) only fetches the first page (per_page: 100) so stale
"taken" issues beyond 100 are missed; replace this single call with a paginated
fetch (use octokit.paginate or loop over page parameters) to collect all pages
for owner/repo with labels: 'taken' and state: 'open' before processing cleanup,
ensuring you iterate through each returned page/item set rather than only using
the first-page response.
🧹 Nitpick comments (3)
.github/workflows/issue-take-untake.yml (3)

56-61: Consider escaping regex metacharacters in marker strings.

The marker strings are used directly in RegExp constructors (lines 59, 86, 206, 210). While the current markers don't contain regex metacharacters, this pattern could break if markers are modified in the future.

♻️ Suggested improvement

Add a helper to escape regex special characters:

function escapeRegex(str) {
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

Then use it when constructing regex patterns:

-? body.replace(new RegExp(`${META_START}[\\s\\S]*?${META_END}`), block.trim())
+? body.replace(new RegExp(`${escapeRegex(META_START)}[\\s\\S]*?${escapeRegex(META_END)}`), block.trim())

167-182: Code duplication between jobs.

Constants (EXPIRE_MS, META_START, etc.) and buildUntakenBanner() are duplicated between the take_untake and cleanup jobs. This increases maintenance burden—if the expiration period or banner format changes, both locations need updating.

Consider extracting shared logic into a reusable action or a separate JavaScript file that both jobs reference. Alternatively, document the duplication clearly with a comment noting both locations must stay in sync.


205-213: Inconsistent meta handling on expiry vs untake.

The cleanup job removes the meta block entirely (line 206), while take_untake resets it to { owner: null, ts: 0 }. While functionally equivalent (both allow re-taking), this inconsistency may confuse future maintainers.

Consider using the same approach in both places—either always remove the block or always reset to null state.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 924da8c and 672970a.

📒 Files selected for processing (2)
  • .github/workflows/issue-take-untake.yml
  • docs/docs/guidelines/CONTRIBUTING.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/docs/guidelines/CONTRIBUTING.md
🔇 Additional comments (4)
.github/workflows/issue-take-untake.yml (4)

1-14: LGTM!

The workflow triggers and permissions are well-configured. Minimal permissions (contents: read, issues: write) follow the principle of least privilege.


16-43: LGTM!

The job condition correctly filters out bot-triggered comments to prevent infinite loops. The metadata markers are uniquely prefixed to avoid collisions with user content.


63-88: LGTM!

The banner functions are well-structured, using GitHub's callout syntax for visual clarity. The updateBanner function correctly handles both replacement and initial insertion cases.


99-114: LGTM!

The early normalization of expired claims (lines 102-104) is a good defensive pattern. The issue-opened handler correctly initializes the metadata and banner state.

Comment thread .github/workflows/issue-take-untake.yml
Comment thread .github/workflows/issue-take-untake.yml Outdated
Comment thread .github/workflows/issue-take-untake.yml
Comment thread .github/workflows/issue-take-untake.yml Outdated
laxitajain and others added 4 commits January 8, 2026 18:58
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

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

🤖 Fix all issues with AI agents
In @docs/docs/guidelines/CONTRIBUTING.md:
- Around line 26-75: Run Prettier to fix formatting by running "prettier --write
docs/docs/guidelines/CONTRIBUTING.md" (or your project's format script) to
satisfy CI; then update the markdown emphasis style for the inline command names
that currently use asterisks (e.g., the `**/take**` and `**/untake**` command
listings rendered on the Claim Commands section) to use underscores instead of
asterisks (e.g., _/take_ and _/untake_) to address markdownlint warnings on
those lines.
🧹 Nitpick comments (3)
.github/workflows/issue-take-untake.yml (3)

135-147: Consider adding reaction-based feedback for failed commands.

When /take fails (issue already claimed or insufficient permissions), the command silently returns with no indication to the user. This could cause confusion.

An alternative to posting comments would be adding a reaction emoji (👎 or ❌) to the command comment on failure, which provides feedback without creating notification noise.

Example implementation
// TAKE
if (takeMatch) {
  if (meta.owner) {
    await github.rest.reactions.createForIssueComment({
      owner, repo, comment_id: comment.id, content: '-1'
    });
    return;
  }
  if (targetUser !== commenter && !isPrivileged) {
    await github.rest.reactions.createForIssueComment({
      owner, repo, comment_id: comment.id, content: '-1'
    });
    return;
  }
  // ... success path, optionally add +1 reaction
}

149-173: /untake @user ignores the mentioned user—consider validating.

The targetUser extracted on line 131 is never checked in the untake flow. If an admin runs /untake @bob expecting to remove Bob's claim, but Alice actually owns the issue, Alice's claim is removed instead.

This doesn't match the documented behavior of removing a specific user's claim. Consider validating that targetUser matches meta.owner when specified:

Proposed fix
 // UNTAKE
 if (untakeMatch) {
+  // If a specific user was mentioned, verify they are the current claimer
+  const mentionedUser = untakeMatch[1];
+  if (mentionedUser && meta.owner !== mentionedUser) return;
+
   // Non-privileged users can only release their own claim
   if (!isPrivileged && meta.owner !== commenter) return;

Also, consider adding a brief comment to the empty catch block on line 165 explaining the intent:

 try {
   await github.rest.issues.removeLabel({ ... });
-} catch {}
+} catch { /* Label may not exist */ }

179-199: Helper functions are duplicated across jobs.

buildUntakenBanner, the marker constants, and meta-parsing logic are duplicated between take_untake and cleanup jobs. While this works, it creates a maintenance burden if the banner format or expiry logic changes.

Consider extracting shared code to a reusable workflow or composite action in the future.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 672970a and 16cfcb8.

📒 Files selected for processing (2)
  • .github/workflows/issue-take-untake.yml
  • docs/docs/guidelines/CONTRIBUTING.md
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-04T15:34:04.654Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 146
File: docs/docs/reference/react/components/ThemeSwitch/tests.md:123-124
Timestamp: 2026-01-04T15:34:04.654Z
Learning: In Docusaurus documentation, relative directory links (e.g., ../ or ../../../hooks/useTheme) resolve to index.md within the target directory. Do not require or force linking to index.md explicitly; such relative links remain valid and should not be changed when reviewing docs under the docs directory.

Applied to files:

  • docs/docs/guidelines/CONTRIBUTING.md
🪛 GitHub Actions: CI
docs/docs/guidelines/CONTRIBUTING.md

[error] 1-1: Prettier formatting check failed. Run 'prettier --write' to fix code style issues. (Command: prettier --check .)

🪛 markdownlint-cli2 (0.18.1)
docs/docs/guidelines/CONTRIBUTING.md

49-49: Emphasis style
Expected: underscore; Actual: asterisk

(MD049, emphasis-style)


49-49: Emphasis style
Expected: underscore; Actual: asterisk

(MD049, emphasis-style)


52-52: Emphasis style
Expected: underscore; Actual: asterisk

(MD049, emphasis-style)


52-52: Emphasis style
Expected: underscore; Actual: asterisk

(MD049, emphasis-style)

🔇 Additional comments (2)
.github/workflows/issue-take-untake.yml (2)

42-43: Potential race condition on concurrent /take commands.

If two users run /take simultaneously, both may read the issue as unclaimed before either update completes, resulting in the second claim silently overwriting the first. This is inherent to GitHub Actions' stateless design.

The window is small, so real-world impact is low, but consider documenting this edge case or adding a mutex-style check (e.g., re-fetching the issue before writing and aborting if it changed).

Also applies to: 144-145


1-14: Workflow structure and permissions look good.

The trigger conditions, permissions scope (contents: read, issues: write), and overall architecture align well with the PR objectives—storing claim state in the issue body eliminates comment noise and unnecessary notifications.

Comment thread docs/docs/guidelines/CONTRIBUTING.md
laxitajain and others added 2 commits January 8, 2026 19:33
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@laxitajain

Copy link
Copy Markdown
Contributor Author

I’ve pushed an updated version that addresses your points.

Metadata storage
The workflow no longer stores internal state in bot comments. Claim metadata is now stored directly in the issue body inside a hidden, structured metadata block. This makes the issue body the single source of truth, avoids comment noise and notifications, and simplifies both parsing and cleanup logic.

Permissions (triage & maintainers)
Claim handling now respects GitHub permission levels:

  • Any user can /take an unclaimed issue for themselves.
  • Users with triage or higher permissions can:
    • remove someone else’s claim (/untake @user)
    • claim an issue on behalf of another user (/take @user)

This allows maintainers to resolve stale or incorrect claims while keeping normal contributor flow intact.

Documentation
I’ve updated the CONTRIBUTING guidelines to document:

  • where claim state is stored,
  • the supported /take and /untake commands,
  • how permission levels affect claim overrides.

Please review, and thank you for your thorough guidance :)

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

🧹 Nitpick comments (2)
docs/docs/guidelines/CONTRIBUTING.md (2)

66-74: Clarify what "inactivity" means for claim expiry.

Line 68 states claims expire after "21 days of inactivity" but does not define whether this means:

  • 21 days since the claim was taken?
  • 21 days since the last comment on the issue (by anyone)?
  • 21 days since the claimee last commented or interacted with the issue?

Add a brief sentence to clarify the inactivity baseline so contributors understand when their claims will expire.


28-36: Consider adding a brief description of the visible banner.

Line 36 mentions "A visible banner is rendered from this metadata to clearly show whether an issue is claimed or unclaimed" but does not describe what the banner looks like, where it appears in the issue, or what content it displays. A short example or description (e.g., "The banner appears at the top of the issue body and shows the claimee's username and claim timestamp") would help contributors recognize and understand the banner when they see it.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16cfcb8 and fb8bec6.

📒 Files selected for processing (1)
  • docs/docs/guidelines/CONTRIBUTING.md
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-04T15:34:04.654Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 146
File: docs/docs/reference/react/components/ThemeSwitch/tests.md:123-124
Timestamp: 2026-01-04T15:34:04.654Z
Learning: In Docusaurus documentation, relative directory links (e.g., ../ or ../../../hooks/useTheme) resolve to index.md within the target directory. Do not require or force linking to index.md explicitly; such relative links remain valid and should not be changed when reviewing docs under the docs directory.

Applied to files:

  • docs/docs/guidelines/CONTRIBUTING.md
🔇 Additional comments (1)
docs/docs/guidelines/CONTRIBUTING.md (1)

26-74: ✅ Solid documentation of the refactored claim workflow.

The new "Claiming Issues" section is well-structured and aligns with the PR objectives and reviewer feedback. Metadata storage in a hidden issue body block, permission-level handling, and centralized guidance are all clearly documented. The relative links to other guide sections follow Docusaurus conventions correctly.

@Ryan-Millard

Copy link
Copy Markdown
Owner

I’ve pushed an updated version that addresses your points.

Metadata storage The workflow no longer stores internal state in bot comments. Claim metadata is now stored directly in the issue body inside a hidden, structured metadata block. This makes the issue body the single source of truth, avoids comment noise and notifications, and simplifies both parsing and cleanup logic.

Permissions (triage & maintainers) Claim handling now respects GitHub permission levels:

  • Any user can /take an unclaimed issue for themselves.

  • Users with triage or higher permissions can:

    • remove someone else’s claim (/untake @user)
    • claim an issue on behalf of another user (/take @user)

This allows maintainers to resolve stale or incorrect claims while keeping normal contributor flow intact.

Documentation I’ve updated the CONTRIBUTING guidelines to document:

  • where claim state is stored,
  • the supported /take and /untake commands,
  • how permission levels affect claim overrides.

Please review, and thank you for your thorough guidance :)

Thank you, @laxitajain! I'm reviewing this now. :)

Ryan-Millard
Ryan-Millard previously approved these changes Jan 8, 2026
@Ryan-Millard
Ryan-Millard merged commit b063c42 into Ryan-Millard:main Jan 8, 2026
3 checks passed
@Ryan-Millard

Copy link
Copy Markdown
Owner

Thank you very much @laxitajain! Your contribution is really going to be useful for the maintainers of this repo.

If you need any help finding another issue to work on, just let me know. I'd be glad to help!

Have a wonderful day!🦔

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.

Fix: Stop posting bot instruction comment on new issues

2 participants