Skip to content

Introduce core configuration, GitHub client, and bot steps for issue …#86

Merged
Sachindu-Nethmin merged 1 commit intosimiligh:mainfrom
Sachindu-Nethmin:feat/scheduled-duplicate-closure
Feb 22, 2026
Merged

Introduce core configuration, GitHub client, and bot steps for issue …#86
Sachindu-Nethmin merged 1 commit intosimiligh:mainfrom
Sachindu-Nethmin:feat/scheduled-duplicate-closure

Conversation

@Sachindu-Nethmin
Copy link
Copy Markdown
Contributor

@Sachindu-Nethmin Sachindu-Nethmin commented Feb 22, 2026

Description

Implements automated closure of duplicate issues after a configurable grace period. When the bot detects a high-confidence duplicate, it now labels the issue as potential-duplicate and warns the author that the issue will be auto-closed in 72 hours unless they object. A new simili auto-close CLI command scans for these labeled issues and closes those whose grace period has expired with no human activity.

Screenshot 2026-02-22 191451

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to change)
  • 📚 Documentation update
  • 🔧 Configuration/build change
  • ♻️ Refactoring (no functional changes)
  • 🧪 Test update

Related Issues

Fixes #57
Relates to #54
Relates to #55

Changes Made

  • Added CloseIssue and RemoveLabel methods to GitHub client (internal/integrations/github/client.go)
  • Added AutoCloseConfig with grace_period_hours (default: 72h) to config (internal/core/config/config.go)
  • Created AutoCloser logic: fetch → grace period check → human activity check → close (internal/steps/auto_closer.go)
  • Created simili auto-close CLI command with --dry-run, --verbose, --grace-minutes flags (cmd/simili/commands/auto_close.go)
  • Added potential-duplicate label to SuggestedLabels when duplicate is detected (internal/steps/duplicate_detector.go)
  • Added 72-hour auto-close warning to the duplicate section of triage comments (internal/steps/response_builder.go)
  • Created scheduled GitHub Actions workflow running daily at 10 AM UTC (.github/workflows/auto-close.yml)
  • Added unit tests for RemoveLabel validation, bot user detection, grace period calculation, and result counts

Testing

  • I have run go build ./... successfully
  • I have run go test ./... successfully
  • I have run go vet ./... successfully
  • I have tested the changes locally

Manual Testing on Sachindu-Nethmin/simili-bot

Test Result
Create issue with potential-duplicate label, run auto-close with --grace-minutes 1 ✅ Issue closed, label swapped to duplicate, comment posted
Create issue with potential-duplicate label + human comment, run auto-close ✅ Issue skipped (skipped_human_activity)
Run auto-close with default 72h grace period on recently labeled issue ✅ Issue skipped (skipped_grace_period)
Run auto-close with --dry-run ✅ Logs actions without side effects

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • New and existing unit tests pass locally with my changes

Additional Notes

  • The --grace-minutes flag is for testing only (not serialized to YAML config). In production, use auto_close.grace_period_hours in simili.yaml.
  • The workflow uses secrets.GH_PAT — ensure this secret has issues:write permission.
  • The .gitignore pattern simili blocks new files under cmd/simili/; auto_close.go was force-added with git add -f.

Summary by CodeRabbit

  • New Features

    • Automatic closure of duplicate issues after a configurable grace period (72 hours default).
    • "Potential-duplicate" label applied to detected duplicates and a warning message posted before auto-close.
    • Configurable dry-run and verbose modes for testing/logging.
    • Scheduled daily run plus manual trigger to run the auto-close workflow.
  • Tests

    • Added tests for auto-close logic, grace-period handling, bot detection, and duplicate detection.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

Adds an automated duplicate-issue closure feature: new AutoCloseConfig, GitHub client methods to remove labels and close issues, an AutoCloser service that processes issues labeled "potential-duplicate" with grace-period and human-activity checks, duplicate detection now suggests the label, and a scheduled GitHub Actions workflow runs the CLI auto-close command.

Changes

Cohort / File(s) Summary
GitHub Actions Workflow
\.github/workflows/auto-close.yml
New scheduled (daily 10:00 UTC) and manual workflow that builds the CLI and runs the auto-close command; supports dry-run input and uses GH_PAT secret for repo writes.
Configuration
internal/core/config/config.go
Adds AutoCloseConfig (GracePeriodHours, GracePeriodMinutesOverride, DryRun) on Config, defaulting GracePeriodHours to 72 and merging child overrides.
GitHub Client Methods & Tests
internal/integrations/github/client.go, internal/integrations/github/client_test.go
Adds CloseIssue and RemoveLabel client methods; RemoveLabel validates input and ignores 404; unit test validates empty-label rejection.
Auto-Closer Implementation & Tests
internal/steps/auto_closer.go, internal/steps/auto_closer_test.go
New AutoCloser with NewAutoCloser and Run to fetch issues labeled potential-duplicate, compute grace period (override if set), detect human activity, post closing comment, swap labels, close issues (dry-run supported), and aggregate results; tests cover bot detection, grace logic, and result counts.
Duplicate Detection Integration
internal/steps/duplicate_detector.go
High-confidence duplicates now append potential-duplicate to suggested labels.
Response Builder Integration
internal/steps/response_builder.go
Appends auto-close grace-period warning (reads AutoClose.GracePeriodHours, defaults to 72) to duplicate response text.

Sequence Diagram

sequenceDiagram
    participant Workflow as "GitHub Actions\nWorkflow"
    participant CLI as "Simili CLI"
    participant AutoCloser as "AutoCloser\nService"
    participant GH as "GitHub API"

    Workflow->>CLI: Run auto-close (daily or manual)
    CLI->>AutoCloser: Init (config, dry-run)
    AutoCloser->>GH: List open issues with "potential-duplicate"
    GH-->>AutoCloser: Return issues

    loop For each issue
        AutoCloser->>GH: Get issue events (find label time)
        GH-->>AutoCloser: Return timeline

        alt Grace period elapsed
            AutoCloser->>GH: List comments since labeled time
            GH-->>AutoCloser: Return comments

            alt No human activity detected
                alt Dry-run
                    AutoCloser->>AutoCloser: Log closure intent
                else Production
                    AutoCloser->>GH: Post closing comment
                    AutoCloser->>GH: Remove "potential-duplicate" label
                    AutoCloser->>GH: Add "duplicate" label
                    AutoCloser->>GH: Close issue
                end
            else Human activity detected
                AutoCloser->>AutoCloser: Skip issue
            end
        else Grace period pending
            AutoCloser->>AutoCloser: Skip issue
        end
    end

    AutoCloser-->>CLI: Return results (closed, skipped, errors)
    CLI-->>Workflow: Exit
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested labels

automation, triage, bot, maintenance

Suggested reviewers

  • Kavirubc

Poem

🐰 Hop, hop — I scan the field of issues wide,
If silence stays where duplicates hide,
With gentle paws I close and sweep the ground,
Carrots saved, clean burrows all around. 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 4

❌ Failed checks (2 warnings, 2 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements automated closure with grace period and label-based filtering, but the linked issue #56 specifically requires LLM-based hybrid reasoning verification before taking action, which is not present in this changeset. Implement LLM-based verification step in duplicate detection [#56] before closing issues, or re-link the PR to issues that describe the automated closure and grace-period features.
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is truncated with ellipsis and does not fully convey the PR's main objective of implementing automated duplicate issue closure. Provide a complete, specific title such as 'Implement automated duplicate issue closure with configurable grace period' to better reflect the PR's primary changes.
Out of Scope Changes check ❓ Inconclusive While most changes align with automated closure logic, the PR objectives mention implementing hybrid LLM reasoning per #56, but the changeset does not include LLM integration or reasoning verification components. Clarify whether LLM-based verification is in-scope for this PR or if it should be addressed in a follow-up PR. If out-of-scope, consider unlinking or clarifying the relationship to #56.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

…auto-closing and duplicate detection.

Signed-off-by: Sachindu-Nethmin <sachindunethminweerasinghe@gmail.com>
@Sachindu-Nethmin Sachindu-Nethmin force-pushed the feat/scheduled-duplicate-closure branch from 80ce85b to 8d064c4 Compare February 22, 2026 15:14
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
internal/steps/auto_closer_test.go (2)

81-93: This test validates its own constants, not production code.

TestAutoCloseResultCounts constructs an AutoCloseResult with hardcoded values and checks that they sum correctly. It doesn't exercise AutoCloser.Run() or any other production logic — it only asserts that 2 + 2 + 1 == 5.

Consider replacing this with an integration-style test that uses a mock GitHub client, runs AutoCloser.Run(), and verifies the resulting counts reflect actual processing decisions.

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

In `@internal/steps/auto_closer_test.go` around lines 81 - 93,
TestAutoCloseResultCounts only validates its own hardcoded AutoCloseResult
constants rather than exercising production logic; replace this self-check with
a test that runs the real flow. Update the test to instantiate an AutoCloser,
inject a mock GitHub client that simulates issues/prs and responses, call
AutoCloser.Run(), and then assert the returned AutoCloseResult fields
(Processed, Closed, SkippedGrace, SkippedHuman) reflect the decisions made by
the AutoCloser logic rather than summing hardcoded values; keep the test named
TestAutoCloseResultCounts and reuse AutoCloseResult for assertions.

39-79: Grace period test re-implements the logic rather than testing the production code.

This test independently computes time.Since(labeledAt) >= gracePeriod instead of calling any function from auto_closer.go. It validates the concept but won't catch regressions if the production grace-period check changes (e.g., to use <= or a different clock source).

Consider extracting the grace-period check in auto_closer.go into a testable helper (e.g., isGracePeriodExpired(labeledAt time.Time, gracePeriod time.Duration) bool) and testing that directly.

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

In `@internal/steps/auto_closer_test.go` around lines 39 - 79, Extract the
grace-period check from auto_closer.go into a small, exported or package-private
helper function (for example isGracePeriodExpired(labeledAt time.Time,
gracePeriod time.Duration) bool) and have the test in auto_closer_test.go call
that helper instead of recomputing time.Since(...) directly; update the
production code to call the new helper so behavior stays identical, and if the
production code uses an injectable clock, mirror that in the helper signature
(or keep it using time.Since for now) so tests exercise the exact same logic as
production.
internal/steps/auto_closer.go (1)

230-259: hasHumanActivity does not paginate — may miss comments beyond the first 100.

Only the first page (100 comments) is fetched. If there are more than 100 comments after the label was applied, human activity could be missed (though this is unlikely within a 72-hour window). Consider adding pagination for completeness, consistent with fetchPotentialDuplicates and ListIssueEvents which both paginate.

Pagination sketch
 func (ac *AutoCloser) hasHumanActivity(ctx context.Context, org, repo string, number int, since time.Time) (bool, error) {
 	opts := &githubapi.IssueListCommentsOptions{
 		Since: &since,
 		ListOptions: githubapi.ListOptions{
 			PerPage: 100,
 		},
 	}
 
-	comments, _, err := ac.github.ListComments(ctx, org, repo, number, opts)
-	if err != nil {
-		return false, err
-	}
-
-	for _, comment := range comments {
-		if comment.User == nil {
-			continue
-		}
-		author := comment.User.GetLogin()
-		if !isBotUser(author, ac.cfg.BotUsers) {
-			if ac.verbose {
-				log.Printf("[auto-closer] #%d: human comment by %q at %v",
-					number, author, comment.CreatedAt.Time)
+	for {
+		comments, resp, err := ac.github.ListComments(ctx, org, repo, number, opts)
+		if err != nil {
+			return false, err
+		}
+		for _, comment := range comments {
+			if comment.User == nil {
+				continue
+			}
+			author := comment.User.GetLogin()
+			if !isBotUser(author, ac.cfg.BotUsers) {
+				if ac.verbose {
+					log.Printf("[auto-closer] #%d: human comment by %q at %v",
+						number, author, comment.CreatedAt.Time)
+				}
+				return true, nil
 			}
-			return true, nil
 		}
+		if resp == nil || resp.NextPage == 0 {
+			break
+		}
+		opts.Page = resp.NextPage
 	}
 
 	return false, nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/steps/auto_closer.go` around lines 230 - 259, hasHumanActivity
currently only fetches the first page of comments via ac.github.ListComments
with githubapi.IssueListCommentsOptions, so add pagination: turn the single call
into a loop that calls ac.github.ListComments repeatedly, passing
opts.ListOptions.Page and appending each returned comments slice, using the
returned resp (response) to break when resp.NextPage == 0; preserve the existing
early-return behavior (return true when a non-bot user is found) and keep the
verbose log path, and ensure opts.ListOptions.Page starts at 1 and increments
from resp.NextPage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/auto-close.yml:
- Line 1: The file .github/workflows/auto-close.yml has CRLF line endings;
convert it to Unix LF endings and commit the change so YAML lint passes (e.g.,
use a line-ending conversion tool or editor to replace CRLF with LF), and
optionally add a .gitattributes entry or adjust Git core.autocrlf to prevent
reintroducing CRLF in future.
- Around line 48-61: The workflow invokes a non-existent CLI command
"./simili-cli auto-close" (the auto-close command and
cmd/simili/commands/auto_close.go were removed), so either remove or replace
that step in .github/workflows/auto-close.yml: update the "Run auto-close" job
to call an existing command (e.g., a current simili-cli subcommand) or delete
the job, or alternatively restore and re-register the auto-close command
implementation (recreate cmd/simili/commands/auto_close.go and ensure it is
added to the root command registration) so "./simili-cli auto-close" succeeds;
pick one approach and make the corresponding change consistently in the workflow
and command registration.

In `@internal/core/config/config.go`:
- Around line 393-399: The AutoClose.DryRun boolean is "sticky" because
child.AutoClose.DryRun is a plain bool and the merge uses if
child.AutoClose.DryRun so a child cannot unset a true parent; change
AutoClose.DryRun to *bool in the AutoCloseConfig type, update mergeConfigs (the
block that inspects child.AutoClose.DryRun/result.AutoClose.DryRun) to test for
nil and assign the pointer value when non-nil, and update any consumers to
handle dereferencing (or use helper accessors) so callers read the actual
boolean value safely; this mirrors the pattern used for Transfer.Enabled and
ensures children can explicitly set false.

In `@internal/steps/auto_closer.go`:
- Around line 276-316: closeIssue currently removes the "potential-duplicate"
label before attempting to close the issue, which can leave the issue
unrecoverable if CloseIssue fails; change the operation order in closeIssue so
you Post the closing comment, then call ac.github.CloseIssue(ctx, org, repo,
number) first, and only if closing succeeds perform label updates (call
ac.github.RemoveLabel and ac.github.AddLabels afterwards), retaining the
existing warning logs for label failures; keep CreateComment before CloseIssue
as-is and return the CloseIssue error unchanged so failures prevent label
mutation.

---

Nitpick comments:
In `@internal/steps/auto_closer_test.go`:
- Around line 81-93: TestAutoCloseResultCounts only validates its own hardcoded
AutoCloseResult constants rather than exercising production logic; replace this
self-check with a test that runs the real flow. Update the test to instantiate
an AutoCloser, inject a mock GitHub client that simulates issues/prs and
responses, call AutoCloser.Run(), and then assert the returned AutoCloseResult
fields (Processed, Closed, SkippedGrace, SkippedHuman) reflect the decisions
made by the AutoCloser logic rather than summing hardcoded values; keep the test
named TestAutoCloseResultCounts and reuse AutoCloseResult for assertions.
- Around line 39-79: Extract the grace-period check from auto_closer.go into a
small, exported or package-private helper function (for example
isGracePeriodExpired(labeledAt time.Time, gracePeriod time.Duration) bool) and
have the test in auto_closer_test.go call that helper instead of recomputing
time.Since(...) directly; update the production code to call the new helper so
behavior stays identical, and if the production code uses an injectable clock,
mirror that in the helper signature (or keep it using time.Since for now) so
tests exercise the exact same logic as production.

In `@internal/steps/auto_closer.go`:
- Around line 230-259: hasHumanActivity currently only fetches the first page of
comments via ac.github.ListComments with githubapi.IssueListCommentsOptions, so
add pagination: turn the single call into a loop that calls
ac.github.ListComments repeatedly, passing opts.ListOptions.Page and appending
each returned comments slice, using the returned resp (response) to break when
resp.NextPage == 0; preserve the existing early-return behavior (return true
when a non-bot user is found) and keep the verbose log path, and ensure
opts.ListOptions.Page starts at 1 and increments from resp.NextPage.

@@ -0,0 +1,61 @@
# =============================================================================
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix line endings: CRLF detected, expected LF.

YAMLlint reports wrong newline characters. Ensure this file uses Unix-style line endings (\n), not Windows-style (\r\n).

# Fix locally:
sed -i 's/\r$//' .github/workflows/auto-close.yml
🧰 Tools
🪛 YAMLlint (1.38.0)

[error] 1-1: wrong new line character: expected \n

(new-lines)

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

In @.github/workflows/auto-close.yml at line 1, The file
.github/workflows/auto-close.yml has CRLF line endings; convert it to Unix LF
endings and commit the change so YAML lint passes (e.g., use a line-ending
conversion tool or editor to replace CRLF with LF), and optionally add a
.gitattributes entry or adjust Git core.autocrlf to prevent reintroducing CRLF
in future.

Comment on lines +48 to +61
- name: Run auto-close
env:
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
run: |
DRY_RUN_FLAG=""
if [ "${{ inputs.dry_run }}" = "true" ]; then
DRY_RUN_FLAG="--dry-run"
fi

./simili-cli auto-close \
--repo "${{ github.repository }}" \
--config .github/simili.yaml \
--verbose \
$DRY_RUN_FLAG
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== Check for simili.yaml file ==="
if [ -f .github/simili.yaml ]; then
  echo "File exists. Content:"
  cat .github/simili.yaml
else
  echo "File not found at .github/simili.yaml"
  find . -name "simili.yaml" -type f 2>/dev/null
fi

Repository: similigh/simili-bot

Length of output: 493


🏁 Script executed:

#!/bin/bash
echo "=== Find and examine config.go file ==="
fd "config.go" -type f | head -5

Repository: similigh/simili-bot

Length of output: 290


🏁 Script executed:

#!/bin/bash
echo "=== Find auto-close command implementation ==="
fd "main.go" -path "*/cmd/*" -type f

Repository: similigh/simili-bot

Length of output: 295


🏁 Script executed:

#!/bin/bash
echo "=== Search for Validate function in config context ==="
rg -n "func.*Validate" --type=go -B 2 -A 10

Repository: similigh/simili-bot

Length of output: 3645


🏁 Script executed:

#!/bin/bash
echo "=== Search for Load function in config.go ==="
rg -n "^func.*Load" internal/core/config/config.go -A 15

Repository: similigh/simili-bot

Length of output: 957


🏁 Script executed:

#!/bin/bash
echo "=== Find auto-close command entry point ==="
find . -path "*/cmd/*" -name "*.go" | grep -i "main\|auto"

Repository: similigh/simili-bot

Length of output: 152


🏁 Script executed:

#!/bin/bash
echo "=== Search for auto-close command directory ==="
find . -type d -name "*auto*" -o -type d -name "*cmd*" | head -10

Repository: similigh/simili-bot

Length of output: 116


🏁 Script executed:

#!/bin/bash
echo "=== Check entire internal/core/config/config.go to understand Load and Validate flow ==="
wc -l internal/core/config/config.go

Repository: similigh/simili-bot

Length of output: 186


🏁 Script executed:

#!/bin/bash
echo "=== Check cmd/simili/main.go for auto-close command ==="
cat -n cmd/simili/main.go | head -150

Repository: similigh/simili-bot

Length of output: 521


🏁 Script executed:

#!/bin/bash
echo "=== Search for auto-close command handler ==="
rg -n "auto-close|AutoClose" cmd/simili/main.go -B 3 -A 20

Repository: similigh/simili-bot

Length of output: 108


🏁 Script executed:

#!/bin/bash
echo "=== Check if there are subcommands or separate files ==="
find cmd/simili -type f -name "*.go"

Repository: similigh/simili-bot

Length of output: 393


🏁 Script executed:

#!/bin/bash
echo "=== Search for auto-close in commands directory ==="
rg -n "auto.close" cmd/simili/commands/ -i

Repository: similigh/simili-bot

Length of output: 114


🏁 Script executed:

#!/bin/bash
echo "=== Check root.go for command definitions ==="
cat -n cmd/simili/commands/root.go

Repository: similigh/simili-bot

Length of output: 1871


🏁 Script executed:

#!/bin/bash
echo "=== List all Go files in cmd/simili/commands ==="
ls -la cmd/simili/commands/

Repository: similigh/simili-bot

Length of output: 720


🏁 Script executed:

#!/bin/bash
echo "=== Search entire repo for auto-close command ==="
rg -n "auto" cmd/ -i --type=go

Repository: similigh/simili-bot

Length of output: 290


🏁 Script executed:

#!/bin/bash
echo "=== Search entire repo for 'auto-close' ==="
rg -n "auto-close" . -i

Repository: similigh/simili-bot

Length of output: 2096


🏁 Script executed:

#!/bin/bash
echo "=== Check what simili-cli is and where it comes from ==="
rg -n "simili-cli" . -i

Repository: similigh/simili-bot

Length of output: 119


🏁 Script executed:

#!/bin/bash
echo "=== Check .github/workflows/auto-close.yml to understand the full context ==="
cat -n .github/workflows/auto-close.yml

Repository: similigh/simili-bot

Length of output: 2235


🏁 Script executed:

#!/bin/bash
echo "=== Check if auto-close is in any of the command files ==="
rg -l "auto.close\|AutoClose" cmd/simili/commands/ -i

Repository: similigh/simili-bot

Length of output: 121


🏁 Script executed:

#!/bin/bash
echo "=== Check batch.go since it might contain the auto-close command ==="
grep -n "auto\|close\|Close" cmd/simili/commands/batch.go | head -20

Repository: similigh/simili-bot

Length of output: 348


🏁 Script executed:

#!/bin/bash
echo "=== Check for 'auto-close' command registration ==="
rg -n "auto" cmd/simili/commands/ -i -B 2 -A 5

Repository: similigh/simili-bot

Length of output: 1587


🏁 Script executed:

#!/bin/bash
echo "=== Check process.go for auto-close ==="
head -100 cmd/simili/commands/process.go

Repository: similigh/simili-bot

Length of output: 3198


🏁 Script executed:

#!/bin/bash
echo "=== Search all Go files for 'auto-close' command definition ==="
rg -n "Use.*auto" cmd/ --type=go

Repository: similigh/simili-bot

Length of output: 126


🏁 Script executed:

#!/bin/bash
echo "=== Check if auto-close is in process.go or any other file ==="
rg -n "auto.close" cmd/simili/commands/ --type=go -i

Repository: similigh/simili-bot

Length of output: 125


🏁 Script executed:

#!/bin/bash
echo "=== Search for AddCommand or command registration ==="
rg -n "AddCommand\|registerCmd\|rootCmd.AddCommand" cmd/simili/commands/ --type=go

Repository: similigh/simili-bot

Length of output: 116


🏁 Script executed:

#!/bin/bash
echo "=== Check full batch.go file for command definition ==="
head -60 cmd/simili/commands/batch.go

Repository: similigh/simili-bot

Length of output: 1529


🏁 Script executed:

#!/bin/bash
echo "=== List all init() functions in commands to see registered commands ==="
rg -n "func init()" cmd/simili/commands/ -A 10

Repository: similigh/simili-bot

Length of output: 5799


🏁 Script executed:

#!/bin/bash
echo "=== Check all .go files in cmd/simili for all cobra command definitions ==="
rg -n "var.*Cmd.*=.*&cobra.Command" cmd/simili/commands/

Repository: similigh/simili-bot

Length of output: 455


🏁 Script executed:

#!/bin/bash
echo "=== Verify if auto-close command exists anywhere in the repo ==="
rg -n "\"auto-close\"|'auto-close'" . --type=go

Repository: similigh/simili-bot

Length of output: 127


🏁 Script executed:

#!/bin/bash
echo "=== Final check: list all available commands in simili-cli ==="
rg -n "rootCmd.AddCommand" cmd/simili/commands/ --type=go

Repository: similigh/simili-bot

Length of output: 377


🏁 Script executed:

#!/bin/bash
echo "=== Verify auto_closer.go exists and what it contains ==="
head -50 internal/steps/auto_closer.go

Repository: similigh/simili-bot

Length of output: 1707


🏁 Script executed:

#!/bin/bash
echo "=== Search for any references to auto_closer in commands ==="
rg -n "auto_closer\|AutoClose" cmd/simili/

Repository: similigh/simili-bot

Length of output: 123


🏁 Script executed:

#!/bin/bash
echo "=== Check if there's an auto-close command file we missed ==="
find cmd/simili/commands -name "*auto*" -o -name "*close*"

Repository: similigh/simili-bot

Length of output: 124


🏁 Script executed:

#!/bin/bash
echo "=== Verify the workflow actually tries to call 'auto-close' ==="
grep -n "auto-close" .github/workflows/auto-close.yml

Repository: similigh/simili-bot

Length of output: 216


🏁 Script executed:

#!/bin/bash
echo "=== Check if auto-close was recently added or in progress ==="
git log --oneline --all --grep="auto-close" 2>/dev/null | head -10

Repository: similigh/simili-bot

Length of output: 295


🏁 Script executed:

#!/bin/bash
echo "=== Search for any TODO or FIXME about auto-close command ==="
rg -n "TODO|FIXME" cmd/simili/commands/ -i | grep -i "auto\|close" -i

Repository: similigh/simili-bot

Length of output: 124


🏁 Script executed:

#!/bin/bash
echo "=== Check git status and current HEAD to understand state ==="
git log --oneline -5
echo ""
echo "=== Check if auto-close command file exists in current HEAD ==="
git show HEAD:cmd/simili/commands/ 2>/dev/null | grep auto

Repository: similigh/simili-bot

Length of output: 304


🏁 Script executed:

#!/bin/bash
echo "=== Look at the recent revert commit ==="
git show --stat 47bbe1c 2>/dev/null | head -30

Repository: similigh/simili-bot

Length of output: 984


🏁 Script executed:

#!/bin/bash
echo "=== Verify what files exist now in cmd/simili/commands ==="
ls -la cmd/simili/commands/
echo ""
echo "=== Check if any file might contain auto-close ==="
find . -name "*.go" -exec grep -l "auto-close" {} \; 2>/dev/null

Repository: similigh/simili-bot

Length of output: 886


The auto-close command does not exist in the codebase.

The workflow calls ./simili-cli auto-close at line 57, but the auto-close command was reverted in commit 47bbe1c (Feb 22). The command file cmd/simili/commands/auto_close.go was removed, and the command is not registered. The workflow will fail immediately with an "unknown command" error when executed.

Either restore the auto-close command implementation or remove this workflow file entirely.

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

In @.github/workflows/auto-close.yml around lines 48 - 61, The workflow invokes
a non-existent CLI command "./simili-cli auto-close" (the auto-close command and
cmd/simili/commands/auto_close.go were removed), so either remove or replace
that step in .github/workflows/auto-close.yml: update the "Run auto-close" job
to call an existing command (e.g., a current simili-cli subcommand) or delete
the job, or alternatively restore and re-register the auto-close command
implementation (recreate cmd/simili/commands/auto_close.go and ensure it is
added to the root command registration) so "./simili-cli auto-close" succeeds;
pick one approach and make the corresponding change consistently in the workflow
and command registration.

Comment on lines +393 to +399
// AutoClose: override if fields are set
if child.AutoClose.GracePeriodHours != 0 {
result.AutoClose.GracePeriodHours = child.AutoClose.GracePeriodHours
}
if child.AutoClose.DryRun {
result.AutoClose.DryRun = child.AutoClose.DryRun
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Child config cannot disable DryRun when parent sets it to true.

Because DryRun is a plain bool, the zero value (false) is indistinguishable from "not set." If a parent config sets dry_run: true, a child config setting dry_run: false will not override it — the if child.AutoClose.DryRun guard only fires for true.

Other boolean fields in the config (e.g., Transfer.Enabled) use *bool to handle this. Consider using *bool for consistency, or document that dry_run is "sticky" once enabled.

Suggested fix using *bool

In AutoCloseConfig:

 type AutoCloseConfig struct {
 	GracePeriodHours           int   `yaml:"grace_period_hours"`
 	GracePeriodMinutesOverride int   `yaml:"-"`
-	DryRun                     bool  `yaml:"dry_run,omitempty"`
+	DryRun                     *bool `yaml:"dry_run,omitempty"`
 }

In mergeConfigs:

-	if child.AutoClose.DryRun {
+	if child.AutoClose.DryRun != nil {
 		result.AutoClose.DryRun = child.AutoClose.DryRun
 	}

Then update consumers to dereference accordingly.

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

In `@internal/core/config/config.go` around lines 393 - 399, The AutoClose.DryRun
boolean is "sticky" because child.AutoClose.DryRun is a plain bool and the merge
uses if child.AutoClose.DryRun so a child cannot unset a true parent; change
AutoClose.DryRun to *bool in the AutoCloseConfig type, update mergeConfigs (the
block that inspects child.AutoClose.DryRun/result.AutoClose.DryRun) to test for
nil and assign the pointer value when non-nil, and update any consumers to
handle dereferencing (or use helper accessors) so callers read the actual
boolean value safely; this mirrors the pattern used for Transfer.Enabled and
ensures children can explicitly set false.

Comment on lines +276 to +316
// closeIssue performs the close: swap labels, post comment, close issue.
func (ac *AutoCloser) closeIssue(ctx context.Context, org, repo string, number int) error {
// Determine effective grace period for display
gracePeriodDisplay := ac.cfg.AutoClose.GracePeriodHours
if gracePeriodDisplay <= 0 {
gracePeriodDisplay = 72
}

// Post closing comment
comment := fmt.Sprintf(
"<!-- simili-bot-auto-close -->\n"+
"### Auto-Closed as Duplicate\n\n"+
"This issue was automatically closed because it was marked as a **potential duplicate** "+
"and no objections were raised during the %d-hour grace period.\n\n"+
"If this was closed in error, please reopen this issue and leave a comment explaining "+
"why it is not a duplicate.\n\n"+
"---\n"+
"<sub>Generated by [Simili Bot](https://github.com/similigh/simili-bot)</sub>",
gracePeriodDisplay,
)

if err := ac.github.CreateComment(ctx, org, repo, number, comment); err != nil {
return fmt.Errorf("failed to post closing comment: %w", err)
}

// Swap labels: remove "potential-duplicate", add "duplicate"
if err := ac.github.RemoveLabel(ctx, org, repo, number, "potential-duplicate"); err != nil {
log.Printf("[auto-closer] Warning: failed to remove 'potential-duplicate' label from #%d: %v", number, err)
}

if err := ac.github.AddLabels(ctx, org, repo, number, []string{"duplicate"}); err != nil {
log.Printf("[auto-closer] Warning: failed to add 'duplicate' label to #%d: %v", number, err)
}

// Close the issue
if err := ac.github.CloseIssue(ctx, org, repo, number); err != nil {
return fmt.Errorf("failed to close issue: %w", err)
}

return nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Operation ordering in closeIssue can leave the issue in an unrecoverable state.

If RemoveLabel("potential-duplicate") succeeds (line 302) but CloseIssue fails (line 311), the issue loses its potential-duplicate label. On the next scheduled run, fetchPotentialDuplicates won't find it — the issue is left open with a duplicate label and a closing comment, but never actually closed, and will never be retried.

Reorder so the label is only removed after the issue is successfully closed:

Proposed fix: close first, then swap labels
 func (ac *AutoCloser) closeIssue(ctx context.Context, org, repo string, number int) error {
 	// ...comment setup omitted...
 
 	if err := ac.github.CreateComment(ctx, org, repo, number, comment); err != nil {
 		return fmt.Errorf("failed to post closing comment: %w", err)
 	}
 
-	// Swap labels: remove "potential-duplicate", add "duplicate"
-	if err := ac.github.RemoveLabel(ctx, org, repo, number, "potential-duplicate"); err != nil {
-		log.Printf("[auto-closer] Warning: failed to remove 'potential-duplicate' label from #%d: %v", number, err)
-	}
-
-	if err := ac.github.AddLabels(ctx, org, repo, number, []string{"duplicate"}); err != nil {
-		log.Printf("[auto-closer] Warning: failed to add 'duplicate' label to #%d: %v", number, err)
-	}
-
 	// Close the issue
 	if err := ac.github.CloseIssue(ctx, org, repo, number); err != nil {
 		return fmt.Errorf("failed to close issue: %w", err)
 	}
 
+	// Swap labels only after successful close
+	if err := ac.github.RemoveLabel(ctx, org, repo, number, "potential-duplicate"); err != nil {
+		log.Printf("[auto-closer] Warning: failed to remove 'potential-duplicate' label from #%d: %v", number, err)
+	}
+
+	if err := ac.github.AddLabels(ctx, org, repo, number, []string{"duplicate"}); err != nil {
+		log.Printf("[auto-closer] Warning: failed to add 'duplicate' label to #%d: %v", number, err)
+	}
+
 	return nil
 }
📝 Committable suggestion

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

Suggested change
// closeIssue performs the close: swap labels, post comment, close issue.
func (ac *AutoCloser) closeIssue(ctx context.Context, org, repo string, number int) error {
// Determine effective grace period for display
gracePeriodDisplay := ac.cfg.AutoClose.GracePeriodHours
if gracePeriodDisplay <= 0 {
gracePeriodDisplay = 72
}
// Post closing comment
comment := fmt.Sprintf(
"<!-- simili-bot-auto-close -->\n"+
"### Auto-Closed as Duplicate\n\n"+
"This issue was automatically closed because it was marked as a **potential duplicate** "+
"and no objections were raised during the %d-hour grace period.\n\n"+
"If this was closed in error, please reopen this issue and leave a comment explaining "+
"why it is not a duplicate.\n\n"+
"---\n"+
"<sub>Generated by [Simili Bot](https://github.com/similigh/simili-bot)</sub>",
gracePeriodDisplay,
)
if err := ac.github.CreateComment(ctx, org, repo, number, comment); err != nil {
return fmt.Errorf("failed to post closing comment: %w", err)
}
// Swap labels: remove "potential-duplicate", add "duplicate"
if err := ac.github.RemoveLabel(ctx, org, repo, number, "potential-duplicate"); err != nil {
log.Printf("[auto-closer] Warning: failed to remove 'potential-duplicate' label from #%d: %v", number, err)
}
if err := ac.github.AddLabels(ctx, org, repo, number, []string{"duplicate"}); err != nil {
log.Printf("[auto-closer] Warning: failed to add 'duplicate' label to #%d: %v", number, err)
}
// Close the issue
if err := ac.github.CloseIssue(ctx, org, repo, number); err != nil {
return fmt.Errorf("failed to close issue: %w", err)
}
return nil
}
// closeIssue performs the close: swap labels, post comment, close issue.
func (ac *AutoCloser) closeIssue(ctx context.Context, org, repo string, number int) error {
// Determine effective grace period for display
gracePeriodDisplay := ac.cfg.AutoClose.GracePeriodHours
if gracePeriodDisplay <= 0 {
gracePeriodDisplay = 72
}
// Post closing comment
comment := fmt.Sprintf(
"<!-- simili-bot-auto-close -->\n"+
"### Auto-Closed as Duplicate\n\n"+
"This issue was automatically closed because it was marked as a **potential duplicate** "+
"and no objections were raised during the %d-hour grace period.\n\n"+
"If this was closed in error, please reopen this issue and leave a comment explaining "+
"why it is not a duplicate.\n\n"+
"---\n"+
"<sub>Generated by [Simili Bot](https://github.com/similigh/simili-bot)</sub>",
gracePeriodDisplay,
)
if err := ac.github.CreateComment(ctx, org, repo, number, comment); err != nil {
return fmt.Errorf("failed to post closing comment: %w", err)
}
// Close the issue
if err := ac.github.CloseIssue(ctx, org, repo, number); err != nil {
return fmt.Errorf("failed to close issue: %w", err)
}
// Swap labels only after successful close
if err := ac.github.RemoveLabel(ctx, org, repo, number, "potential-duplicate"); err != nil {
log.Printf("[auto-closer] Warning: failed to remove 'potential-duplicate' label from #%d: %v", number, err)
}
if err := ac.github.AddLabels(ctx, org, repo, number, []string{"duplicate"}); err != nil {
log.Printf("[auto-closer] Warning: failed to add 'duplicate' label to #%d: %v", number, err)
}
return nil
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/steps/auto_closer.go` around lines 276 - 316, closeIssue currently
removes the "potential-duplicate" label before attempting to close the issue,
which can leave the issue unrecoverable if CloseIssue fails; change the
operation order in closeIssue so you Post the closing comment, then call
ac.github.CloseIssue(ctx, org, repo, number) first, and only if closing succeeds
perform label updates (call ac.github.RemoveLabel and ac.github.AddLabels
afterwards), retaining the existing warning logs for label failures; keep
CreateComment before CloseIssue as-is and return the CloseIssue error unchanged
so failures prevent label mutation.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
internal/steps/auto_closer.go (1)

261-274: isBotUser has inconsistent case-sensitivity for built-in patterns.

HasSuffix(author, "[bot]") and HasPrefix(author, "gh-simili") are case-sensitive, while EqualFold(author, "simili-bot") is case-insensitive. GitHub usernames are case-insensitive, so "GH-SIMILI-BOT" would not be recognized as a bot by the prefix check. Normalizing to lowercase before all checks is more robust:

♻️ Proposed refactor
 func isBotUser(author string, configBotUsers []string) bool {
-	if strings.HasSuffix(author, "[bot]") ||
-		strings.HasPrefix(author, "gh-simili") ||
-		strings.EqualFold(author, "simili-bot") {
+	lower := strings.ToLower(author)
+	if strings.HasSuffix(lower, "[bot]") ||
+		strings.HasPrefix(lower, "gh-simili") ||
+		lower == "simili-bot" {
 		return true
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/steps/auto_closer.go` around lines 261 - 274, The isBotUser function
uses mixed case-sensitivity (strings.HasSuffix/HasPrefix are case-sensitive
while strings.EqualFold is not); normalize the author string with
strings.ToLower at the start of isBotUser (e.g., authorLower :=
strings.ToLower(author)) and then perform suffix/prefix/equality checks against
lowercased literal patterns ("[bot]", "gh-simili", "simili-bot"); for
configBotUsers either compare with strings.EqualFold or pre-normalize entries
(strings.ToLower) and compare against authorLower to ensure all checks are
case-insensitive.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/integrations/github/client.go`:
- Around line 80-85: The nil dereference happens when asserting err as
*github.ErrorResponse and then reading ghErr.Response.StatusCode in the
RemoveLabelForIssue error handling; guard against a nil Response by checking
ghErr.Response != nil (or use errors.As to assert the type first) before
accessing StatusCode and only treat 404 as non-error when ghErr.Response is
non-nil and StatusCode == 404; update the error branch in the code around
c.client.Issues.RemoveLabelForIssue and the github.ErrorResponse check to return
nil only after confirming ghErr.Response is not nil and StatusCode == 404.

In `@internal/steps/auto_closer_test.go`:
- Around line 81-93: The test TestAutoCloseResultCounts assumes
Closed+SkippedGrace+SkippedHuman == Processed, but Run() actually increments
Processed for errors too; update the test (or add a complementary test) to
assert Closed + SkippedGrace + SkippedHuman + len(Errors) == Processed for
AutoCloseResult, and/or create a second case that populates Errors to validate
the error path; refer to AutoCloseResult fields (Processed, Closed,
SkippedGrace, SkippedHuman, Errors) and the Run() behavior when implementing the
fix.

In `@internal/steps/auto_closer.go`:
- Around line 250-253: The verbose logging line can panic because
comment.CreatedAt is a *github.Timestamp and may be nil; update the ac.verbose
logging branch (the log.Printf call that references comment.CreatedAt.Time with
variables number and author) to guard against nil by using
comment.GetCreatedAt() or by checking comment.CreatedAt != nil and using its
Time when present (falling back to a zero/placeholder time or skipping the
timestamp) before calling log.Printf so the code never dereferences a nil
CreatedAt.
- Around line 278-295: The closing comment currently uses gracePeriodDisplay
derived only from cfg.AutoClose.GracePeriodHours, ignoring the CLI override
(GracePeriodMinutesOverride) so the message can be wrong; update the code to use
the actually computed grace period (the variable used to decide closure, e.g.
gracePeriod or the value computed in Run) for the display text instead of
GracePeriodHours, and modify closeIssue (and its call site in Run) to accept
that computed grace period so the comment printed by closeIssue reflects the
real minutes/hours used for the decision; touch the symbols gracePeriodDisplay,
GracePeriodHours, GracePeriodMinutesOverride, closeIssue and Run to pass and
render the correct value.

---

Duplicate comments:
In @.github/workflows/auto-close.yml:
- Line 1: The workflow file contains CRLF line endings (look for the header line
"#
=============================================================================");
convert this file to LF endings, commit the change, and ensure future files are
normalized by adding or updating .gitattributes (e.g., set YAML files to eol=lf
or use "* text=auto eol=lf") or by running a line-ending normalization tool
(dos2unix/git config core.autocrlf) before committing so the
.github/workflows/*.yml files use LF consistently.
- Around line 48-61: The workflow step "Run auto-close" is invoking a
non-existent binary './simili-cli auto-close'; update the run command to a valid
invocation (for example use npx to run the installed package: replace
'./simili-cli auto-close' with 'npx simili-cli auto-close') or point to the
correct executable path in the repo, keeping the same flags (--repo, --config,
--verbose, $DRY_RUN_FLAG) and preserving the DRY_RUN_FLAG logic that reads the
inputs.dry_run value.

In `@internal/core/config/config.go`:
- Around line 393-399: mergeConfigs currently treats AutoClose.DryRun as a
sticky boolean by only copying it when true, which prevents explicitly setting
DryRun=false in child configs; change the AutoClose.DryRun field to a tri-state
(preferably *bool) or add an explicit DryRunSet flag in the AutoClose struct,
update parsing/unmarshal code so the presence of the field is preserved, and
modify mergeConfigs (the block handling result.AutoClose and child.AutoClose) to
check the new indicator (nil vs non-nil or DryRunSet) and assign
result.AutoClose.DryRun from child.AutoClose when the child explicitly sets the
value; adjust any consumers of AutoClose.DryRun accordingly (e.g., places
reading AutoClose.DryRun) to handle the pointer/flag semantics.

In `@internal/steps/auto_closer.go`:
- Around line 276-315: The current closeIssue flow removes the
"potential-duplicate" label before closing the issue, which can leave issues in
an unrecoverable state if the close fails; change the operation order in
closeIssue so you post the comment (CreateComment), then call CloseIssue first,
and only after a successful close attempt perform the label swaps (RemoveLabel
and AddLabels) while still logging warnings on failure — keep the existing error
returns for CreateComment and CloseIssue and retain the warning-style logging
for RemoveLabel/AddLabels so label errors do not block the close.

---

Nitpick comments:
In `@internal/steps/auto_closer.go`:
- Around line 261-274: The isBotUser function uses mixed case-sensitivity
(strings.HasSuffix/HasPrefix are case-sensitive while strings.EqualFold is not);
normalize the author string with strings.ToLower at the start of isBotUser
(e.g., authorLower := strings.ToLower(author)) and then perform
suffix/prefix/equality checks against lowercased literal patterns ("[bot]",
"gh-simili", "simili-bot"); for configBotUsers either compare with
strings.EqualFold or pre-normalize entries (strings.ToLower) and compare against
authorLower to ensure all checks are case-insensitive.

Comment on lines +80 to +85
_, err := c.client.Issues.RemoveLabelForIssue(ctx, org, repo, number, label)
if err != nil {
// If the label wasn't on the issue, GitHub returns 404 — not an error for us.
if ghErr, ok := err.(*github.ErrorResponse); ok && ghErr.Response.StatusCode == 404 {
return nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against a nil Response before accessing StatusCode.

If ErrorResponse.Response is nil (e.g., when the error is constructed outside the normal HTTP flow), the dereference at line 83 panics.

🛡️ Proposed fix
-		if ghErr, ok := err.(*github.ErrorResponse); ok && ghErr.Response.StatusCode == 404 {
+		if ghErr, ok := err.(*github.ErrorResponse); ok && ghErr.Response != nil && ghErr.Response.StatusCode == 404 {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/integrations/github/client.go` around lines 80 - 85, The nil
dereference happens when asserting err as *github.ErrorResponse and then reading
ghErr.Response.StatusCode in the RemoveLabelForIssue error handling; guard
against a nil Response by checking ghErr.Response != nil (or use errors.As to
assert the type first) before accessing StatusCode and only treat 404 as
non-error when ghErr.Response is non-nil and StatusCode == 404; update the error
branch in the code around c.client.Issues.RemoveLabelForIssue and the
github.ErrorResponse check to return nil only after confirming ghErr.Response is
not nil and StatusCode == 404.

Comment on lines +81 to +93
func TestAutoCloseResultCounts(t *testing.T) {
result := &AutoCloseResult{
Processed: 5,
Closed: 2,
SkippedGrace: 2,
SkippedHuman: 1,
}

total := result.Closed + result.SkippedGrace + result.SkippedHuman
if total != result.Processed {
t.Errorf("counts don't add up: closed(%d) + grace(%d) + human(%d) = %d, want %d",
result.Closed, result.SkippedGrace, result.SkippedHuman, total, result.Processed)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

TestAutoCloseResultCounts tests an invariant that doesn't hold in the presence of errors.

The Run() method increments Processed for every issue — including error cases — but errors only append to result.Errors without incrementing any counter. The true invariant is:

Closed + SkippedGrace + SkippedHuman + len(Errors) == Processed

The test as written asserts 2+2+1 == 5 against a hand-constructed struct with no errors, which is trivially true and doesn't exercise real Run() output. Consider updating the assertion or adding a complementary test case with errors:

 total := result.Closed + result.SkippedGrace + result.SkippedHuman
+totalWithErrors := total + len(result.Errors)
-if total != result.Processed {
-    t.Errorf(...)
+if totalWithErrors != result.Processed {
+    t.Errorf("counts don't add up: closed(%d) + grace(%d) + human(%d) + errors(%d) = %d, want %d",
+        result.Closed, result.SkippedGrace, result.SkippedHuman, len(result.Errors), totalWithErrors, result.Processed)
 }
📝 Committable suggestion

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

Suggested change
func TestAutoCloseResultCounts(t *testing.T) {
result := &AutoCloseResult{
Processed: 5,
Closed: 2,
SkippedGrace: 2,
SkippedHuman: 1,
}
total := result.Closed + result.SkippedGrace + result.SkippedHuman
if total != result.Processed {
t.Errorf("counts don't add up: closed(%d) + grace(%d) + human(%d) = %d, want %d",
result.Closed, result.SkippedGrace, result.SkippedHuman, total, result.Processed)
}
func TestAutoCloseResultCounts(t *testing.T) {
result := &AutoCloseResult{
Processed: 5,
Closed: 2,
SkippedGrace: 2,
SkippedHuman: 1,
}
total := result.Closed + result.SkippedGrace + result.SkippedHuman
totalWithErrors := total + len(result.Errors)
if totalWithErrors != result.Processed {
t.Errorf("counts don't add up: closed(%d) + grace(%d) + human(%d) + errors(%d) = %d, want %d",
result.Closed, result.SkippedGrace, result.SkippedHuman, len(result.Errors), totalWithErrors, result.Processed)
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/steps/auto_closer_test.go` around lines 81 - 93, The test
TestAutoCloseResultCounts assumes Closed+SkippedGrace+SkippedHuman == Processed,
but Run() actually increments Processed for errors too; update the test (or add
a complementary test) to assert Closed + SkippedGrace + SkippedHuman +
len(Errors) == Processed for AutoCloseResult, and/or create a second case that
populates Errors to validate the error path; refer to AutoCloseResult fields
(Processed, Closed, SkippedGrace, SkippedHuman, Errors) and the Run() behavior
when implementing the fix.

Comment on lines +250 to +253
if ac.verbose {
log.Printf("[auto-closer] #%d: human comment by %q at %v",
number, author, comment.CreatedAt.Time)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Nil dereference on comment.CreatedAt in verbose path.

comment.CreatedAt is *github.Timestamp. If it is nil, accessing .Time at line 252 panics. While GitHub always returns created_at for comments in normal operation, this path is unguarded.

🛡️ Proposed fix
-		if ac.verbose {
-			log.Printf("[auto-closer] #%d: human comment by %q at %v",
-				number, author, comment.CreatedAt.Time)
-		}
+		if ac.verbose {
+			var createdAt interface{} = "<unknown>"
+			if comment.CreatedAt != nil {
+				createdAt = comment.CreatedAt.Time
+			}
+			log.Printf("[auto-closer] #%d: human comment by %q at %v",
+				number, author, createdAt)
+		}
📝 Committable suggestion

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

Suggested change
if ac.verbose {
log.Printf("[auto-closer] #%d: human comment by %q at %v",
number, author, comment.CreatedAt.Time)
}
if ac.verbose {
var createdAt interface{} = "<unknown>"
if comment.CreatedAt != nil {
createdAt = comment.CreatedAt.Time
}
log.Printf("[auto-closer] #%d: human comment by %q at %v",
number, author, createdAt)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/steps/auto_closer.go` around lines 250 - 253, The verbose logging
line can panic because comment.CreatedAt is a *github.Timestamp and may be nil;
update the ac.verbose logging branch (the log.Printf call that references
comment.CreatedAt.Time with variables number and author) to guard against nil by
using comment.GetCreatedAt() or by checking comment.CreatedAt != nil and using
its Time when present (falling back to a zero/placeholder time or skipping the
timestamp) before calling log.Printf so the code never dereferences a nil
CreatedAt.

Comment on lines +278 to +295
// Determine effective grace period for display
gracePeriodDisplay := ac.cfg.AutoClose.GracePeriodHours
if gracePeriodDisplay <= 0 {
gracePeriodDisplay = 72
}

// Post closing comment
comment := fmt.Sprintf(
"<!-- simili-bot-auto-close -->\n"+
"### Auto-Closed as Duplicate\n\n"+
"This issue was automatically closed because it was marked as a **potential duplicate** "+
"and no objections were raised during the %d-hour grace period.\n\n"+
"If this was closed in error, please reopen this issue and leave a comment explaining "+
"why it is not a duplicate.\n\n"+
"---\n"+
"<sub>Generated by [Simili Bot](https://github.com/similigh/simili-bot)</sub>",
gracePeriodDisplay,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Closing comment misreports grace period when GracePeriodMinutesOverride is active.

gracePeriodDisplay reads only cfg.AutoClose.GracePeriodHours, ignoring the CLI GracePeriodMinutesOverride. With --grace-minutes 5 the issue gets a comment saying "72-hour grace period" when only 5 minutes were required — misleading in test runs and audits.

🐛 Proposed fix: pass the computed grace period into `closeIssue`

Change the signature to accept the actual grace period:

-func (ac *AutoCloser) closeIssue(ctx context.Context, org, repo string, number int) error {
-	gracePeriodDisplay := ac.cfg.AutoClose.GracePeriodHours
-	if gracePeriodDisplay <= 0 {
-		gracePeriodDisplay = 72
-	}
+func (ac *AutoCloser) closeIssue(ctx context.Context, org, repo string, number int, gracePeriod time.Duration) error {
+	gracePeriodDisplay := int(gracePeriod.Hours())
+	if gracePeriodDisplay <= 0 {
+		gracePeriodDisplay = 1
+	}

And pass gracePeriod at the call site in Run:

-		if err := ac.closeIssue(ctx, org, repo, number); err != nil {
+		if err := ac.closeIssue(ctx, org, repo, number, gracePeriod); err != nil {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/steps/auto_closer.go` around lines 278 - 295, The closing comment
currently uses gracePeriodDisplay derived only from
cfg.AutoClose.GracePeriodHours, ignoring the CLI override
(GracePeriodMinutesOverride) so the message can be wrong; update the code to use
the actually computed grace period (the variable used to decide closure, e.g.
gracePeriod or the value computed in Run) for the display text instead of
GracePeriodHours, and modify closeIssue (and its call site in Run) to accept
that computed grace period so the comment printed by closeIssue reflects the
real minutes/hours used for the decision; touch the symbols gracePeriodDisplay,
GracePeriodHours, GracePeriodMinutesOverride, closeIssue and Run to pass and
render the correct value.

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.

Enhancement: Scheduled auto-close workflow for confirmed duplicates

1 participant