Skip to content

Fix false positive in transfer loop prevention #27

@Kavirubc

Description

@Kavirubc

Problem

The current transfer loop prevention in gatekeeper has a false positive issue. It skips ALL recently created issues with action="opened", including legitimate new issues that were created directly in the repo.

Current Behavior

// This skips BOTH transferred AND new issues
if ctx.Issue.EventAction == "opened" && age < 2*time.Minute {
    return ErrSkipPipeline // TOO AGGRESSIVE
}

Evidence

Test issue #38 in event-integrator-cli:

  • Created directly in CLI repo (NOT transferred)
  • Bot skipped it: "recently created issue (likely transferred)"
  • ❌ False positive - legitimate new issue was not triaged

Root Cause

When GitHub transfers an issue:

  • Source repo gets: action="transferred"
  • Destination repo gets: action="opened"

Our heuristic (skip if opened + age < 2min) can't distinguish:

  1. Transferred issue (should skip)
  2. Real new issue (should NOT skip)

Both appear identical in the webhook event.


Proposed Solution

Implement proper transfer detection using GitHub API:

func (s *Gatekeeper) checkIfRecentlyTransferred(ctx *pipeline.Context) (bool, error) {
    // Query GitHub API for issue events
    events, err := githubClient.ListIssueEvents(
        ctx.Issue.Org,
        ctx.Issue.Repo, 
        ctx.Issue.Number,
    )
    
    // Look for recent transfer event (< 2 minutes)
    for _, event := range events {
        if event.Type == "transferred" && time.Since(event.CreatedAt) < 2*time.Minute {
            return true, nil
        }
    }
    
    return false, nil
}

Implementation Requirements

1. Add GitHub Client to Context

Currently, pipeline.Context doesn't have access to GitHub client. Need to:

  • Add GitHubClient field to Dependencies
  • Pass dependencies through Context
  • Or store client in gatekeeper constructor

2. Implement ListIssueEvents

The GitHub client needs a method to fetch issue timeline events:

func (c *Client) ListIssueEvents(ctx context.Context, org, repo string, number int) ([]Event, error)

3. Update Gatekeeper Logic

// Check EventAction first (fast path for source repos)
if ctx.Issue.EventAction == "transferred" {
    return ErrSkipPipeline
}

// For "opened" events, check API to see if it was actually transferred
if ctx.Issue.EventAction == "opened" {
    if wasTransferred, err := s.checkIfRecentlyTransferred(ctx); err == nil && wasTransferred {
        return ErrSkipPipeline
    }
}

Trade-offs

Pros ✅

  • Accurate: No false positives
  • Reliable: Checks actual transfer events
  • Correct behavior: Real new issues get triaged

Cons ⚠️

  • API call: Adds 100-200ms latency per issue
  • Rate limits: Uses GitHub API quota
  • Complexity: More code to maintain

Mitigation

  • Cache transfer status for 5 minutes to avoid repeated API calls
  • Only check for "opened" events (not all events)
  • Fast-fail if API is unavailable (proceed with triage)

Acceptance Criteria

  • GitHub client accessible in gatekeeper
  • ListIssueEvents method implemented
  • Gatekeeper checks API for transfer events
  • False positives eliminated (new issues get triaged)
  • Transfer loops still prevented (transferred issues skipped)
  • Error handling for API failures
  • Tests added for both scenarios

Test Cases

Test 1: Real New Issue

  • Create issue directly in repo
  • Bot should TRIAGE (not skip)
  • ✅ No transfer event in timeline

Test 2: Transferred Issue

  • Create issue in repo A
  • Transfer to repo B
  • Bot should SKIP in repo B
  • ✅ Transfer event found in timeline

Test 3: Old Issue Edited

  • Edit issue created 1 day ago
  • Bot should TRIAGE normally
  • ✅ Transfer check skipped (not "opened" event)

Current Workaround

Revert the time-based heuristic and accept transfer loops until proper fix is implemented.


References

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions