Skip to content

fix(e2e): resolve Forgejo fork conflict by enumerating existing forks - #1699

Open
kasemAlem wants to merge 1 commit into
konflux-ci:mainfrom
kasemAlem:fix-forgejo-test-repo
Open

fix(e2e): resolve Forgejo fork conflict by enumerating existing forks#1699
kasemAlem wants to merge 1 commit into
konflux-ci:mainfrom
kasemAlem:fix-forgejo-test-repo

Conversation

@kasemAlem

Copy link
Copy Markdown
Contributor

On 409 Conflict from CreateFork, Forgejo enforces one fork per org per source repo. The previous handler called GetRepo with the requested target name, which fails when a stale fork from a prior test run exists under a different name — producing the consistent CI failure:

fork of X to Y already exists but failed to fetch: The target couldn't be found.

Replace the GetRepo lookup with ListForks so the 409 path finds whichever fork already exists in targetOwner's namespace, regardless of its name.

Maintainers will complete the following section

@qodo-app-for-konflux-ci

Copy link
Copy Markdown

PR Summary by Qodo

Handle Forgejo fork conflicts by discovering existing organization forks

🐞 Bug fix 🕐 10-20 Minutes

Grey Divider

AI Description

• Finds stale organization forks when Forgejo rejects duplicate fork creation.
• Reuses existing forks even when their repository names differ from the requested target.
• Returns contextual errors when fork enumeration fails or finds no matching owner.
Diagram

graph TD
  A["Fork request"] --> B["Create fork"] --> C{"Created?"}
  C -- Yes --> D["Return repository"]
  C -- No --> E{"409 conflict?"}
  E -- No --> H["Return error"]
  E -- Yes --> F["List source forks"] --> G{"Owner fork found?"}
  G -- Yes --> D
  G -- No --> H
Loading
High-Level Assessment

The chosen approach is appropriate because Forgejo's uniqueness constraint concerns the source and target organization rather than the requested repository name. Directly listing source forks avoids destructive stale-fork deletion and fixes the failure mode that a name-based GetRepo lookup cannot handle.

Files changed (1) +15 / -4

Bug fix (1) +15 / -4
git.goDiscover existing Forgejo forks after creation conflicts +15/-4

Discover existing Forgejo forks after creation conflicts

• Replaces the requested-name lookup on HTTP 409 with source fork enumeration. The client now returns the fork in the target owner's namespace regardless of its repository name and provides specific errors for listing or matching failures.

e2e-tests/pkg/clients/forgejo/git.go

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 6, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:17 PM UTC · Completed 5:29 PM UTC

Commit: 9ee3c25 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $2.62

@qodo-app-for-konflux-ci

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (1)

Grey Divider


Action required

1. Tests still target a missing repository 🐞 Bug ≡ Correctness
Description
ForkRepository returns a differently named f on conflict, but the git.ForgejoClient wrapper
discards that repository and preserves no resolved project ID. When a stale fork has the different
name this branch is designed to handle, branch creation, repository URLs, and cleanup all continue
using reportingRepository, which does not exist.
Code

e2e-tests/pkg/clients/forgejo/git.go[R258-259]

+				if strings.HasPrefix(f.FullName, prefix) {
+					return f, nil
Relevance

●●● Strong

Discarding the recovered fork leaves downstream operations targeting the nonexistent requested
repository.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The low-level client returns the discovered repository, but the adapter immediately discards it. The
sole integration caller then constructs its URL, creates a branch, and performs cleanup using the
originally requested reportingRepository, so a recovered fork with another FullName cannot be
used.

e2e-tests/pkg/clients/forgejo/git.go[256-262]
e2e-tests/pkg/clients/git/forgejo.go[162-165]
e2e-tests/tests/integration-service/forgejo-integration-reporting.go[82-101]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The conflict path returns a stale fork under a different name, but the public adapter discards the returned repository. Subsequent test operations continue using the originally requested repository path and therefore fail.

## Issue Context
Ensure successful conflict recovery leaves a repository available at the requested target path. This can be done by replacing or renaming the stale fork before returning, or by propagating the resolved full name through every downstream operation and cleanup path.

## Fix Focus Areas
- e2e-tests/pkg/clients/forgejo/git.go[237-266]
- e2e-tests/pkg/clients/git/forgejo.go[162-165]
- e2e-tests/tests/integration-service/forgejo-integration-reporting.go[82-101]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Fork conflict recovery can regress 📜 Skill insight ▣ Testability
Description
ForkRepository adds conflict-recovery logic for listing and selecting an existing fork, but the
package has no colocated *_test.go file covering the changed function. The 409 Conflict,
listing-error, matching-owner, and no-match branches therefore lack the required happy-path, error,
and edge-case unit coverage.
Code

e2e-tests/pkg/clients/forgejo/git.go[R250-253]

+			forks, _, listErr := fc.client.ListForks(sourceOwner, sourceRepo, forgejo.ListForksOptions{
+				ListOptions: forgejo.ListOptions{Page: 1, PageSize: 50},
+			})
+			if listErr != nil {
Relevance

●●● Strong

Explicit repository rules require colocated tests for changed Go logic and its error and edge-case
branches.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rules 513 and 2067 require changed Go logic to have corresponding unit tests covering successful,
erroneous, and edge-case behavior. The changed branch adds multiple outcomes at lines 250-262, while
the e2e-tests/pkg/clients/forgejo package contains no colocated test file.

Rule 513: Co-locate unit tests with code and use Ginkgo + envtest
e2e-tests/pkg/clients/forgejo/git.go[250-262]
Skill: pr-definition-of-done

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Add colocated Ginkgo unit tests for the new `ForkRepository` conflict-recovery behavior.

## Issue Context
The tests should exercise a `409 Conflict`, successful discovery of a differently named fork in the target owner's namespace, a `ListForks` failure, and a fork list with no matching owner. Use a controllable Forgejo client abstraction or test server so each response can be asserted deterministically.

## Fix Focus Areas
- e2e-tests/pkg/clients/forgejo/git.go[245-262]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Fork recovery fails beyond 50 results 🐞 Bug ☼ Reliability
Description
ForkRepository hard-codes page 1 with a page size of 50 and never inspects any later page. When
the existing fork for targetOwner appears after those results, the conflict recovery returns
“could not be found” even though Forgejo reported that the fork exists.
Code

e2e-tests/pkg/clients/forgejo/git.go[R250-252]

+			forks, _, listErr := fc.client.ListForks(sourceOwner, sourceRepo, forgejo.ListForksOptions{
+				ListOptions: forgejo.ListOptions{Page: 1, PageSize: 50},
+			})
Relevance

●●● Strong

Single-page enumeration deterministically misses valid forks beyond 50, contradicting the recovery
behavior described by the PR.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The changed code makes one ListForks call and searches only its result. The vendored SDK
serializes Page and PageSize into one request without automatic pagination, and documents that
page numbering starts at one.

e2e-tests/pkg/clients/forgejo/git.go[250-262]
vendor/codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v3/fork.go[17-32]
vendor/codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v3/list_options.go[16-44]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The conflict recovery searches only the first page of forks, capped at 50 entries. An existing fork on any later page remains undiscovered.

## Issue Context
Paginate until the matching owner is found or all pages are exhausted. Alternatively, use pagination-disabled listing if this Forgejo endpoint reliably supports that SDK option.

## Fix Focus Areas
- e2e-tests/pkg/clients/forgejo/git.go[250-262]
- vendor/codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v3/fork.go[17-32]
- vendor/codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v3/list_options.go[16-44]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 40 rules
Review mode: ⚖️ Balanced: This is a localized runtime change in the Forgejo fork-recovery path, with behavior depending on API semantics and namespace filtering, so it warrants a careful single-pass review.

Grey Divider

Tip of the day
💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread e2e-tests/pkg/clients/forgejo/git.go Outdated
Comment thread e2e-tests/pkg/clients/forgejo/git.go Outdated
Comment thread e2e-tests/pkg/clients/forgejo/git.go Outdated
@codecov-commenter

codecov-commenter commented Sep 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 68.90%. Comparing base (ef6599a) to head (1b13923).

❗ There is a different number of reports uploaded between BASE (ef6599a) and HEAD (1b13923). Click for more details.

HEAD has 1 upload less than BASE
Flag BASE (ef6599a) HEAD (1b13923)
e2e-tests 1 0
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1699      +/-   ##
==========================================
- Coverage   73.97%   68.90%   -5.07%     
==========================================
  Files          74       74              
  Lines       10617    10617              
==========================================
- Hits         7854     7316     -538     
- Misses       2006     2534     +528     
- Partials      757      767      +10     
Flag Coverage Δ
e2e-tests ?
unit-tests 68.90% <ø> (-0.12%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.
see 39 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ef6599a...1b13923. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@fullsend-ai-review fullsend-ai-review Bot added the risk/low PR risk: low label Sep 6, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 6, 2026

Copy link
Copy Markdown

Risk Assessment: low (1/5)

Details

Single-file XS fix to e2e test utility code with minimal churn, single known author, no protected paths, no dependency or CI changes, and a stable git history.

Previous run

Risk Assessment: low (1/5)

Details

Single-file XS fix to e2e test utility code with minimal churn, single known author, no protected paths, no dependency or CI changes, and a stable git history; the only elevated signal is the test_file_ratio of 0.00 (the file lacks a _test.go suffix despite living in e2e-tests/), which inflates Tier 1 but does not represent a real production coverage gap.

Previous run (2)

Risk Assessment: low (1/5)

Details

Single XS file change (1 file, 19 lines) in a test utility package with a focused bug fix for Forgejo fork conflict resolution; low churn, single author, no protected paths, no dependency or CI changes.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 6, 2026

Copy link
Copy Markdown

Review

Findings

Low

  • [error-handling] e2e-tests/pkg/clients/forgejo/git.go:266 — When DeleteRepo fails on the stale fork, a 404 Not Found error (e.g., from a concurrent deletion) is treated as fatal. The separate DeleteRepository method (line 296–307) already swallows 404 errors, establishing a pattern that could be followed here.
    Remediation: Check whether the delete error indicates a 404 Not Found and, if so, treat it as a successful deletion (set deleted = true and continue to the retry).

  • [error-handling-idiom] e2e-tests/pkg/clients/forgejo/git.go:258 — The error message "fork of %s already exists in %s but failed to list forks: %w" deviates from the file’s consistent "failed to <verb> <subject>: %w" pattern (seen at lines 26, 100, 267, etc.).
    Remediation: Rephrase to follow the prevailing pattern, e.g.: "failed to list forks of %s in %s: %w".

  • [scope-coherence] e2e-tests/pkg/clients/forgejo/git.go:253 — The 409 conflict path now deletes a repository via fc.client.DeleteRepo, which is a destructive side effect for a function named ForkRepository. The inline comment explains the rationale, but the function’s godoc (lines 233–236) does not mention that it may delete a stale fork on conflict.

  • [missing-authorization] e2e-tests/pkg/clients/forgejo/git.go — No linked Jira issue is present. The commit scope is e2e rather than a STONEINTG-* ticket ID, which deviates from the project’s stated convention.

  • [edge-case] e2e-tests/pkg/clients/forgejo/git.go:281 — The retry CreateFork does not handle a second 409 Conflict. If another concurrent process creates a fork between the stale-fork deletion and the retry, the error returned would not attempt another list-and-delete cycle.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run

Review

Findings

Medium

  • [logic-error] e2e-tests/pkg/clients/forgejo/git.go:267 — If no fork in the ListForks results has a FullName starting with the targetOwner prefix, the for-loop completes without deleting anything and execution falls through to the retry CreateFork. That retry will hit the same 409 Conflict because the conflicting fork was never deleted. The error message “after removing stale fork” is misleading since no fork was actually removed. This can happen if the stale fork is on a later page (only the first 50 forks are fetched), or if the FullName format differs from expectations.
    Remediation: Track whether a fork was actually deleted (e.g., a boolean flag set inside the loop). If no fork was found and deleted, return a descriptive error immediately instead of retrying.

Low

  • [process-traceability] e2e-tests/pkg/clients/forgejo/git.go — The PR commit scope uses e2e rather than a Jira ticket ID. CLAUDE.md mandates conventional commits with a Jira ticket as scope (e.g. fix(STONEINTG-XXXX): ...). This is a process/traceability observation, not a code defect.

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (2)

Review

Findings

Low

  • [edge-case] e2e-tests/pkg/clients/forgejo/git.go:250ListForks only fetches the first page (PageSize: 50). If a source repo accumulates more than 50 forks and the target owner's fork falls beyond the first page, the function returns a misleading "could not be found in fork list" error. Additionally, PageSize: 50 deviates from the PageSize: 100 used by GetPullRequests at line 76.
    Remediation: Either paginate through all results until the fork is found or the list is exhausted, or increase PageSize to 100 for consistency with GetPullRequests.

  • [commit-convention] e2e-tests/pkg/clients/forgejo/git.go — The PR title uses fix(e2e) as the scope rather than a Jira ticket (e.g., fix(STONEINTG-XXXX)), which deviates from the convention documented in CLAUDE.md.
    Remediation: Reference an existing STONEINTG Jira ticket in the commit scope, or open one if none exists.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

fullsend-ai-review[bot]

This comment was marked as outdated.

@kasemAlem
kasemAlem force-pushed the fix-forgejo-test-repo branch from 52f887b to 1e6243d Compare September 6, 2026 19:00
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 6, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 7:01 PM UTC · Ended 7:02 PM UTC

Commit: 9ee3c25 · View workflow run →

@kasemAlem
kasemAlem force-pushed the fix-forgejo-test-repo branch from 1e6243d to a86cbda Compare September 6, 2026 19:02
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 6, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:03 PM UTC · Completed 7:17 PM UTC

Commit: 9ee3c25 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $3.55

fullsend-ai-review[bot]

This comment was marked as outdated.

@konflux-ci-qe-bot

Copy link
Copy Markdown

Scenario: konflux-e2e
@kasemAlem: The following test has Failed, say /retest to rerun failed tests.

PipelineRun Name Status Rerun command Build Log Test Log
konflux-e2e-qcbl4 Failed /retest View Pipeline Log View Test Logs

Inspecting Test Artifacts

To inspect your test artifacts, follow these steps:

  1. Install ORAS (see the ORAS installation guide).
  2. Download artifacts with the following commands:
mkdir -p oras-artifacts
cd oras-artifacts
oras pull quay.io/konflux-test-storage/konflux-team/integration-service:konflux-e2e-qcbl4

Test results analysis

🚨 Error occurred while running the E2E tests, list of failed Spec(s):

➡️ [failed] [It] [integration-service-suite Status Reporting of Integration tests] with status reporting of Integration tests in CheckRuns when Integration PipelineRuns completes successfully leads to triggering a push PipelineRun [integration-service, github-status-reporting]

Click to view logs

Timed out after 600.005s.
timed out when waiting for the PipelineRun to start for the component stat-rep-2sup/test-component-pac-7n3oub
Expected success, but got an error:
    <*errors.errorString | 0xc0007547b0>: 
    no pipelinerun found for component test-component-pac-7n3oub
    {
        s: "no pipelinerun found for component test-component-pac-7n3oub",
    }

➡️ [failed] [It] [integration-service-suite Creation of group snapshots for monorepo and multiple repos] with status reporting of Integration tests in CheckRuns when both the init PaC PRs are merged wait for the last components build to finish [integration-service, group-snapshot-creation]

Click to view logs

Expected success, but got an error:
    <*errors.errorString | 0xc000068e90>: 
    PipelineRun cannot be created for the Component group-ho3z/konflux-test-integration-clone-ponipn
    {
        s: "PipelineRun cannot be created for the Component group-ho3z/konflux-test-integration-clone-ponipn",
    }

OCI Artifact Browser URL

View in Artifact Browser

On 409 Conflict from CreateFork, Forgejo enforces one fork per org per
source repo. The previous handler called GetRepo with the *requested*
target name, which fails when a stale fork from a prior test run exists
under a *different* name — producing the consistent CI failure:

fork of X to Y already exists but failed to fetch:
The target couldn't be found.

Replace the GetRepo lookup with ListForks so the 409 path finds
whichever fork already exists in targetOwner's namespace, regardless
of its name.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Kasem Alem <kalem@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 7, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:00 AM UTC · Completed 2:13 AM UTC

Commit: 9ee3c25 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $3.16

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

for _, f := range forks {
if strings.HasPrefix(f.FullName, prefix) {
staleName := strings.TrimPrefix(f.FullName, prefix)
if _, delErr := fc.client.DeleteRepo(targetOwner, staleName); delErr != 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.

[low] error-handling

When DeleteRepo fails on the stale fork, a 404 Not Found error (e.g., from a concurrent deletion) is treated as fatal. The separate DeleteRepository method (line 296-307) already swallows 404 errors, establishing a pattern that could be followed here.

Suggested fix: Check whether the delete error indicates a 404 Not Found and, if so, treat it as a successful deletion (set deleted = true and continue to the retry).

ListOptions: forgejo.ListOptions{Page: page, PageSize: 100},
})
if listErr != nil {
return nil, fmt.Errorf("fork of %s already exists in %s but failed to list forks: %w", sourceProjectID, targetOwner, listErr)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] error-handling-idiom

The error message 'fork of %s already exists in %s but failed to list forks: %w' deviates from the file's consistent 'failed to : %w' pattern (seen at lines 26, 100, 267, etc.).

Suggested fix: Rephrase to follow the prevailing pattern, e.g.: 'failed to list forks of %s in %s: %w'.

// the name being correct for branch creation and cleanup.
prefix := targetOwner + "/"
deleted := false
for page := 1; !deleted; page++ {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] scope-coherence

The 409 conflict path now deletes a repository via fc.client.DeleteRepo, which is a destructive side effect for a function named ForkRepository. The inline comment explains the rationale, but the function's godoc (lines 233-236) does not mention that it may delete a stale fork on conflict.

return nil, fmt.Errorf("fork of %s conflicts in %s but no matching fork found in fork list", sourceProjectID, targetOwner)
}
// Retry now that the stale fork is gone.
forkedRepo, _, retryErr := fc.client.CreateFork(sourceOwner, sourceRepo, forgejo.CreateForkOption{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] edge-case

The retry CreateFork does not handle a second 409 Conflict. If another concurrent process creates a fork between the stale-fork deletion and the retry, the error returned would not attempt another list-and-delete cycle.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants