Skip to content

Comments

fix(auto-closer): fix comment-before-close, missing pagination, merge and test bugs#87

Merged
gh-simili-bot merged 1 commit intomainfrom
fix/auto-closer-bugs
Feb 25, 2026
Merged

fix(auto-closer): fix comment-before-close, missing pagination, merge and test bugs#87
gh-simili-bot merged 1 commit intomainfrom
fix/auto-closer-bugs

Conversation

@Kavirubc
Copy link
Member

@Kavirubc Kavirubc commented Feb 25, 2026

Summary

  • [High] closeIssue: moved closing comment to after the issue is confirmed closed — previously a failed CloseIssue call would leave a misleading "Auto-Closed as Duplicate" comment on a still-open issue
  • [High] hasHumanActivity: added full pagination loop so comments beyond the first 100 are checked — previously human activity on high-traffic issues could be silently missed, causing legitimate issues to be wrongly auto-closed
  • [Low] mergeConfigs: AutoClose.DryRun is now always copied from the child config, allowing a child to explicitly override a parent's dry_run: true back to false
  • [Medium] TestAutoCloseResultCounts: updated assertion to include len(Errors) in the total so the test accurately reflects real runs where some issues hit error paths

Test plan

  • go build ./... passes cleanly
  • go test ./internal/steps/... ./internal/core/config/... passes
  • Reviewed operation order in closeIssue: close → swap labels → comment
  • Reviewed pagination loop in hasHumanActivity mirrors pattern used in fetchPotentialDuplicates and ListIssueEvents

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Fixed human activity detection to scan all available comments for more accurate results, rather than checking only the initial page.
  • Improvements

    • Enhanced DryRun configuration handling to support explicit false settings.
    • Refined issue closing workflow to prevent posting closing comments if the close operation fails.
  • Tests

    • Improved test coverage with enhanced validation scenarios.

@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

📝 Walkthrough

Walkthrough

This pull request modifies the auto-closer configuration and logic across three files. It changes how DryRun is propagated in configuration merging, updates the hasHumanActivity function to paginate through all comment pages, and reorders the closeIssue operation sequence to close issues before managing labels and posting comments. Additionally, test coverage is refactored to use table-driven testing for result count validation.

Changes

Cohort / File(s) Summary
Configuration
internal/core/config/config.go
DryRun value in AutoClose config is now always copied unconditionally in mergeConfigs instead of only when child.AutoClose.DryRun is true, allowing explicit false settings to propagate.
Auto-closer Logic
internal/steps/auto_closer.go
hasHumanActivity now paginates through all comment pages to detect human activity; closeIssue flow reordered to close issue first, then manage labels, then post comment, with failures logged as warnings instead of errors.
Tests
internal/steps/auto_closer_test.go
TestAutoCloseResultCounts refactored from inline result struct to table-driven approach with scenarios for error and non-error cases, validating totalProcessed calculation.

Sequence Diagram

sequenceDiagram
    participant Client as Caller
    participant Closer as closeIssue()
    participant IssueSvc as Issue Service
    participant LabelSvc as Label Service
    participant CommentSvc as Comment Service

    Client->>Closer: closeIssue(issue, labels, comment)
    Closer->>IssueSvc: Close issue
    IssueSvc-->>Closer: Close successful
    
    Closer->>LabelSvc: Remove "potential-duplicate" label
    LabelSvc-->>Closer: Label removed
    
    Closer->>LabelSvc: Add "duplicate" label
    LabelSvc-->>Closer: Label added
    
    Closer->>CommentSvc: Post closing comment
    CommentSvc-->>Closer: Comment posted / Error
    
    alt Comment Success
        Closer-->>Client: Return nil (success)
    else Comment Failure
        Closer-->>Client: Log warning, return nil
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

fix

Poem

🐰 Hop, hop, through pages we go,
Each comment checked before we close,
Labels dance, then issues rest,
Auto-closer passes the test!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the four main fixes: comment-before-close ordering, pagination for activity detection, merge config behavior, and test assertion updates.

✏️ 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
  • Commit unit tests in branch fix/auto-closer-bugs

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.

… and test bugs

- closeIssue: post closing comment AFTER issue is confirmed closed to
  prevent ghost "Auto-Closed" comments on issues that failed to close
- hasHumanActivity: paginate through all comment pages so human activity
  beyond the first 100 comments is never silently missed
- mergeConfigs: always copy AutoClose.DryRun from child so a child config
  can explicitly override a parent's dry_run: true back to false
- TestAutoCloseResultCounts: include error count in the total so the
  assertion holds for runs that encounter partial failures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Kavirubc <hapuarachchikaviru@gmail.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request addresses four bugs in the auto-closer functionality for duplicate issues: (1) reordering operations in closeIssue to prevent misleading comments on failed closes, (2) adding pagination to hasHumanActivity to avoid missing comments beyond the first 100, (3) updating the config merge logic for DryRun to allow child configs to override parent settings, and (4) fixing test assertions to properly account for error cases.

Changes:

  • Reordered closeIssue to close issue first, then swap labels, then comment (prevents "closed" comments on still-open issues if close fails)
  • Added full pagination loop to hasHumanActivity to check all comments, not just the first 100
  • Modified mergeConfigs to always copy DryRun from child config
  • Updated TestAutoCloseResultCounts to include error count in total validation

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
internal/steps/auto_closer.go Reordered close operation sequence and added comment pagination loop
internal/core/config/config.go Changed DryRun merge logic to always copy from child config
internal/steps/auto_closer_test.go Enhanced test to validate error counting and updated author attribution

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if child.AutoClose.DryRun {
result.AutoClose.DryRun = child.AutoClose.DryRun
}
result.AutoClose.DryRun = child.AutoClose.DryRun
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The unconditional copy of DryRun will cause unintended behavior when a child config doesn't explicitly set dry_run in YAML. Since DryRun is a plain bool (not *bool), it defaults to false when not specified. This means a child config that doesn't mention dry_run will always override a parent's dry_run: true setting back to false, which is likely not the intended behavior.

To properly support overriding a parent's dry_run: true with dry_run: false, the field should be changed to *bool type (like Transfer.Enabled and other boolean config fields that need to distinguish between "not set" and "explicitly set to false"). Then the merge logic should check if child.AutoClose.DryRun != nil before copying.

Alternatively, if the current behavior is intentional and child configs are always expected to explicitly specify dry_run, this should be documented clearly.

Copilot uses AI. Check for mistakes.
@gh-simili-bot
Copy link
Contributor

🧪 E2E Test

Bot responded: yes

Test repo → gh-simili-bot/simili-e2e-22379609803
Run → logs

Auto-generated by E2E pipeline

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/core/config/config.go`:
- Around line 393-398: The AutoClose.DryRun field must be changed from bool to
*bool (update the AutoCloseConfig type) so we can distinguish "unset" vs
"explicit false"; in mergeConfigs (where result.AutoClose.DryRun is currently
unconditionally assigned from child.AutoClose.DryRun) only copy the value when
child.AutoClose.DryRun != nil, leaving the parent value intact otherwise; update
NewAutoCloser (auto_closer.go) and any other code that reads
cfg.AutoClose.DryRun to handle the nil case (nil means inherit/absent — use the
parent/default behavior or explicitly coerce to false where intended).

In `@internal/steps/auto_closer.go`:
- Around line 252-255: The verbose log can panic when IssueComment.CreatedAt is
nil because the code accesses comment.CreatedAt.Time directly; replace that
access with the nil-safe accessor by calling comment.GetCreatedAt() and using
its .Time (e.g., createdAt := comment.GetCreatedAt(); use createdAt.Time in the
log) or guard with an explicit nil check before logging; update the log in the
auto-closer block where ac.verbose is checked to use comment.GetCreatedAt() to
avoid the nil pointer dereference.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e09ca0f and 40a2968.

📒 Files selected for processing (3)
  • internal/core/config/config.go
  • internal/steps/auto_closer.go
  • internal/steps/auto_closer_test.go

Comment on lines +393 to +398
// AutoClose: override if fields are set.
// DryRun is always copied so a child config can explicitly set it to false.
if child.AutoClose.GracePeriodHours != 0 {
result.AutoClose.GracePeriodHours = child.AutoClose.GracePeriodHours
}
if child.AutoClose.DryRun {
result.AutoClose.DryRun = child.AutoClose.DryRun
}
result.AutoClose.DryRun = child.AutoClose.DryRun
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

DryRun bool can't distinguish "child omitted it" from "child explicitly set it to false"

yaml:"dry_run,omitempty" means a child config that simply doesn't mention dry_run will have child.AutoClose.DryRun == false after unmarshal — identical to one that explicitly writes dry_run: false. The unconditional assignment therefore silently overrides a parent's dry_run: true whenever the child is silent, defeating its safety-guard purpose.

Every other tri-state boolean in this file uses *bool for exactly this reason (e.g. CrossRepoSearch, Transfer.Enabled, Transfer.LLMRoutingEnabled).

🛡️ Proposed fix — use *bool for DryRun

In AutoCloseConfig:

-DryRun bool `yaml:"dry_run,omitempty"` // If true, log actions without executing
+DryRun *bool `yaml:"dry_run,omitempty"` // If true, log actions without executing

In mergeConfigs:

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

In NewAutoCloser (auto_closer.go):

-dryRun: dryRun || cfg.AutoClose.DryRun,
+dryRun: dryRun || (cfg.AutoClose.DryRun != nil && *cfg.AutoClose.DryRun),

Any read of cfg.AutoClose.DryRun elsewhere would likewise need a nil-guard.

🤖 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 - 398, The AutoClose.DryRun
field must be changed from bool to *bool (update the AutoCloseConfig type) so we
can distinguish "unset" vs "explicit false"; in mergeConfigs (where
result.AutoClose.DryRun is currently unconditionally assigned from
child.AutoClose.DryRun) only copy the value when child.AutoClose.DryRun != nil,
leaving the parent value intact otherwise; update NewAutoCloser (auto_closer.go)
and any other code that reads cfg.AutoClose.DryRun to handle the nil case (nil
means inherit/absent — use the parent/default behavior or explicitly coerce to
false where intended).

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

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Nil pointer dereference on comment.CreatedAt.Time in verbose log

IssueComment.CreatedAt is *github.Timestamp; if the field is nil, accessing .Time panics. Although the GitHub API reliably populates created_at, defensive access in a hot loop is worth the one-liner cost.

🛡️ Proposed fix
-                if ac.verbose {
-                    log.Printf("[auto-closer] #%d: human comment by %q at %v",
-                        number, author, comment.CreatedAt.Time)
-                }
+                if ac.verbose {
+                    log.Printf("[auto-closer] #%d: human comment by %q at %v",
+                        number, author, comment.GetCreatedAt().Time)
+                }

GetCreatedAt() returns a zero-value Timestamp instead of panicking when the field is nil.

#!/bin/bash
# Verify that IssueComment.CreatedAt is a pointer type in go-github v60
# and that GetCreatedAt() is a safe nil-aware accessor.
rg -n "CreatedAt" --type go -C2 | grep -A2 "IssueComment"
🤖 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 252 - 255, The verbose log can
panic when IssueComment.CreatedAt is nil because the code accesses
comment.CreatedAt.Time directly; replace that access with the nil-safe accessor
by calling comment.GetCreatedAt() and using its .Time (e.g., createdAt :=
comment.GetCreatedAt(); use createdAt.Time in the log) or guard with an explicit
nil check before logging; update the log in the auto-closer block where
ac.verbose is checked to use comment.GetCreatedAt() to avoid the nil pointer
dereference.

@gh-simili-bot gh-simili-bot merged commit f5d26ab into main Feb 25, 2026
12 checks passed
@gh-simili-bot gh-simili-bot deleted the fix/auto-closer-bugs branch February 25, 2026 02:40
@github-project-automation github-project-automation bot moved this from Todo to Done in simili-bot-v1-release Feb 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working e2e

Projects

Development

Successfully merging this pull request may close these issues.

2 participants