Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,25 @@ run-backport-agent-e2e-tests:
-e BACKPORT_E2E_EXCLUDE_ISSUES="$(BACKPORT_E2E_EXCLUDE_ISSUES)" \
backport-agent-e2e-tests

.PHONY: run-backport-agent-konflux-e2e-tests
run-backport-agent-konflux-e2e-tests:
# Konflux build backend variant of the backport e2e. Unlike Copr (which
# builds from a local SRPM), Konflux builds from a real pushed fork ref:
# even under DRY_RUN the workflow creates the fork under FORK_NAMESPACE,
# pushes the backport branch (carrying the fixture's pre-fix history), and
# submits a real Konflux build; only the MR/Jira writes stay suppressed.
# The dist-git SOURCE stays mocked (local bare clones via insteadOf).
# Requires FORK_NAMESPACE + GITLAB_TOKEN + KONFLUX_* in the environment /
# .secrets/mcp-gateway.env and a reachable Konflux cluster.
MOCK_JIRA=true DRY_RUN=true BUILD_BACKEND=konflux FORK_NAMESPACE="$(FORK_NAMESPACE)" \
$(COMPOSE) -f $(COMPOSE_FILE) --profile=e2e-test run --rm \
-e MOCK_JIRA="true" \
-e DRY_RUN="true" \
-e BUILD_BACKEND="konflux" \
-e RUN_LLM_JUDGE=$(RUN_LLM_JUDGE) \
-e BACKPORT_E2E_EXCLUDE_ISSUES="$(BACKPORT_E2E_EXCLUDE_ISSUES)" \
backport-agent-e2e-tests


.PHONY: run-reproducer-agent-e2e-tests
run-reproducer-agent-e2e-tests:
Expand Down
17 changes: 16 additions & 1 deletion compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ x-beeai-env: &beeai-env
MAX_CONCURRENT_TASKS: ${MAX_CONCURRENT_TASKS:-1}
LOG_BUFFER_SIZE: ${LOG_BUFFER_SIZE:-0}
DRY_RUN: ${DRY_RUN:-false}
BUILD_BACKEND: ${BUILD_BACKEND:-copr}
JIRA_DRY_RUN: ${JIRA_DRY_RUN:-false}
JIRA_ALLOW_STATUS_CHANGES: ${JIRA_ALLOW_STATUS_CHANGES:-false}
ERRATA_ALLOW_STATUS_CHANGES: ${ERRATA_ALLOW_STATUS_CHANGES:-false}
Expand Down Expand Up @@ -121,7 +122,21 @@ services:
- TESTING_FARM_DRY_RUN=${TESTING_FARM_DRY_RUN:-false}
- TESTING_FARM_COMPOSE_FILTER=${TESTING_FARM_COMPOSE_FILTER:-}
- GIT_REPO_BASEPATH=/git-repos
- FORK_NAMESPACE=${FORK_NAMESPACE:-}
# FORK_NAMESPACE is sourced from .secrets/mcp-gateway.env (env_file).
# Do NOT re-declare it here: a compose `environment:` entry overrides
# env_file, so `${FORK_NAMESPACE:-}` would blank the secret whenever the
# host var is unset, breaking fork push auth (_is_private_gitlab). A host
# override is still possible via `podman-compose run -e FORK_NAMESPACE=...`.
# Selects the build tool set (copr default | konflux). Konflux also needs
# the KONFLUX_* vars (API/PIPELINE urls + token) and GITLAB_TOKEN, supplied
# via .secrets/mcp-gateway.env. Every build targets one fixed, generic
# Component in ymir-tenant (ymir-scratch-build): its build-service-provisioned
# ServiceAccount carries the appstudio-pipelines-scc and its image repo
# receives the scratch build, while git-url + revision are overridden per run.
# The target defaults (namespace/SA/image repo) are baked into konflux.py and
# need no env; override only for a different deployment via KONFLUX_NAMESPACE,
# KONFLUX_SERVICE_ACCOUNT, and KONFLUX_IMAGE_REPO.
- BUILD_BACKEND=${BUILD_BACKEND:-copr}
- JIRA_MOCK_FILES=ymir/tools/privileged/tests/data
# e2e tests write insteadOf rewrites here; ignored when the file is absent
- GIT_CONFIG_GLOBAL=${GIT_REPO_BASEPATH:-/git-repos}/.mock_gitconfig
Expand Down
361 changes: 233 additions & 128 deletions ymir/agents/backport_agent.py

Large diffs are not rendered by default.

22 changes: 20 additions & 2 deletions ymir/agents/build_agent.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncio
import logging
import os
from typing import Any
from urllib.parse import urlsplit

Expand Down Expand Up @@ -39,6 +40,16 @@
logger = logging.getLogger(__name__)


def build_backend() -> str:
"""Return the configured build backend ("copr" default, or "konflux")."""
return os.getenv("BUILD_BACKEND", "copr").strip().lower()


def is_konflux_backend() -> bool:
"""True when builds are driven by Konflux (build-from-git-ref) rather than Copr."""
return build_backend() == "konflux"


class BuildState(BaseModel):
build_input: BuildInputSchema
build_result: BuildResult | None = None
Expand Down Expand Up @@ -86,13 +97,20 @@ async def execute_build(state: BuildState) -> str:

state.output = BuildOutputSchema(
success=False,
error=result.error_message or "Copr build failed without an error message",
error=result.error_message or "Build failed without an error message",
is_timeout=result.is_timeout,
)
if result.is_timeout:
return Workflow.END

if not any(urlsplit(url).path.endswith(".log.gz") for url in result.artifacts_urls or []):
# Copr advertises gzipped build logs; Konflux returns authenticated
# Kubearchive pod-log URLs (no .log.gz suffix). Diagnose whenever the
# backend handed us any log URLs to inspect.
if is_konflux_backend():
has_logs = bool(result.artifacts_urls)
else:
has_logs = any(urlsplit(url).path.endswith(".log.gz") for url in result.artifacts_urls or [])
if not has_logs:
return Workflow.END

return "diagnose_failure"
Expand Down
65 changes: 33 additions & 32 deletions ymir/agents/prompts/backport/prompt_fix_build_error.j2
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ The cherry-pick workflow succeeded but the build failed:

{{ build_error }}

You do NOT build the package yourself. Your job is to produce a corrected
backport: fix the patch(es), regenerate them, and regenerate the SRPM. A
separate build step then rebuilds your fix. If the build still fails you will be
called again with the new build error, so make ONE focused fix attempt per
invocation.

CRITICAL CONSTRAINTS:
- The upstream repository at {{ local_clone }}-upstream has all your previous work intact.
DO NOT clone it again. DO NOT reset to base commit.
Expand All @@ -40,7 +46,7 @@ CRITICAL CONSTRAINTS:
Do NOT modify or remove existing BuildRequires/Requires entries.

NEVER modify the spec for:
* Environmental issues (COPR vs RHEL builder differences like unbuffer/expect wrappers,
* Environmental issues (build-environment differences like unbuffer/expect wrappers,
pipefail behavior, locale settings) — report success=false instead
* Pre-existing build issues unrelated to your patch
* Working around missing or too-old dependencies not yet in the buildroot
Expand All @@ -49,11 +55,11 @@ CRITICAL CONSTRAINTS:

IF rules do NOT explicitly allow it (or no rules exist):
NEVER modify the spec file — the build worked before your patches; fix the patches instead.
The build runs in COPR, not on official RHEL builders. COPR environments may have
differences (e.g. unbuffer/expect wrappers, pipefail behavior, locale settings) that
can cause spurious failures unrelated to your patches. If the failure is caused by the
build environment rather than by your code changes, report success=false and explain
the environmental issue — do not modify the spec to work around it.
The build environment may differ from your local environment (e.g. unbuffer/expect
wrappers, pipefail behavior, locale settings) and can cause spurious failures unrelated
to your patches. If the failure is caused by the build environment rather than by your
code changes, report success=false and explain the environmental issue — do not modify
the spec to work around it.

- DO NOT modify anything in {{ local_clone }} dist-git repository except:
a. The backport patch file(s) you created (by regenerating them from upstream repo)
Expand All @@ -63,13 +69,13 @@ CRITICAL CONSTRAINTS:
Read the spec file to find the patch filenames you added — do NOT assume the name.

- DO NOT commit any changes in {{ local_clone }} dist-git repository during the fix attempt.
Keep all dist-git changes (patch files and spec edits) uncommitted until the build passes.
Commit source fixes in {{ local_clone }}-upstream, staging only the intended source files.
Keep build logs and repair notes in {{ build_logs_dir }}, outside both Git repositories.
Keep all dist-git changes (patch files and spec edits) uncommitted — the build step
commits them. Commit source fixes in {{ local_clone }}-upstream, staging only the intended
source files. Keep repair notes in {{ build_logs_dir }}, outside both Git repositories.
Do not copy these diagnostics into either repository or include them in generated patches.

- Fix BOTH compilation errors AND test failures. NEVER skip or disable tests.
- Make ONE attempt — you will be called again if the build still fails.
- Make ONE attempt — the build step will re-invoke you if the build still fails.

Before you start: Read {{ build_logs_dir }}/fix-attempts.md for a log of
previous fix attempts. Do NOT repeat strategies that already failed.
Expand All @@ -89,7 +95,7 @@ WORKFLOW:
IMPORTANT: If the tools fail to fetch rules (returns an error, timeout, etc.),
treat this as "NOT allowed" — do NOT add any spec entries, fix patches only.

1. Analyze the build error and identify what's missing (functions, types, headers, etc.)
1. Analyze the build error above and identify what's missing (functions, types, headers, etc.)

2. If the build error indicates missing dependencies that are DIRECTLY INTRODUCED by your
backported patch AND rules (from step 0) explicitly allow it:
Expand All @@ -98,7 +104,7 @@ WORKFLOW:
- If needed during build (%build or %check) AND at runtime: add both BuildRequires and Requires
- Additions only — do NOT modify or remove existing entries
- See CRITICAL CONSTRAINTS and Criterion 2 for validation requirements
- Then proceed to step 6 to rebuild and verify
- Then proceed to step 6 to regenerate the SRPM

3. Otherwise, explore {{ local_clone }}-upstream to find solutions — use git log, git show, grep,
and view files. The full upstream history is available.
Expand All @@ -121,23 +127,17 @@ SPECIAL CONSIDERATIONS FOR TEST FAILURES:
- repository_path: {{ local_clone }}-upstream
- patch_file_path: the path to each patch file in {{ local_clone }}/

6. Test the build:
- Use the `run_package_prep` tool to verify patches apply cleanly
- Use the `build_srpm` tool to generate a SRPM
- Call `build_package` with the SRPM path, dist_git_branch, and jira_issue
- If build fails: use `download_artifacts` to get logs and identify the new error
{% if has_extract_log_snippets %}
- Use `extract_log_snippets` with `log_path` pointing to `builder-live.log`
(or `root.log` if unavailable) to extract the most relevant snippets
and identify the new error
{% endif %}
6. Regenerate the SRPM so the build step can rebuild your fix:
- Use the `run_package_prep` tool to verify your patches apply cleanly
- Use the `build_srpm` tool to generate a fresh SRPM that includes your fix
Do NOT attempt to build the package yourself. The workflow's build step performs
the only build and will call you again with the new error if it still fails.

7. Append a summary to {{ build_logs_dir }}/fix-attempts.md documenting:
- What you identified as the root cause
- Which commits you cherry-picked or what manual edits you made
- Any BuildRequires or Requires additions to the spec file (if rules allowed
adding new entries), including what was added and why
- The build result (pass/fail and error if applicable)

8. Self-Review: Before reporting a result, verify your work meets all criteria
below. Run `git diff HEAD -- *.spec` in {{ local_clone }} to inspect what
Expand Down Expand Up @@ -172,12 +172,12 @@ SPECIAL CONSIDERATIONS FOR TEST FAILURES:
- Appears in the build error (missing header, undefined reference, "command not found"
during %build/%check, etc.)
- Is used by code in your backported patch (verify by reading the patch)
- Is not a workaround for COPR environmental differences
- Is not a workaround for build-environment differences
For Requires additions:
- Is introduced by your backported patch (installed binary calls a new executable,
dlopen()s a library, imports a Python module, etc.)
- Evidence comes from the patch diff or runtime test failures
- Is not a workaround for COPR environmental differences
- Is not a workaround for build-environment differences
For both BuildRequires and Requires on the same package:
- Justified when the dependency is needed during build (%build or %check) AND by the
installed package at runtime
Expand Down Expand Up @@ -223,22 +223,22 @@ SPECIAL CONSIDERATIONS FOR TEST FAILURES:
- Working around buildroot limitations (dependency not available or too old)

If the build failure is caused by something unrelated to the patch content
(e.g. COPR environment, dependency version not yet available), report
(e.g. the build environment, dependency version not yet available), report
success=false and explain the issue instead of working around it.

If ALL criteria pass, report success as normal.

If ANY criterion fails, attempt to fix the problem before reporting failure:
- Criterion 1, 3, or 6 (bad patch content, disabled tests, or unrelated
changes): return to step 4 and re-fix the issue, then regenerate patches
(step 5) and rebuild (step 6). For criterion 6, revert unrelated hunks
from {{ local_clone }}-upstream before regenerating.
(step 5) and regenerate the SRPM (step 6). For criterion 6, revert unrelated
hunks from {{ local_clone }}-upstream before regenerating.
- Criterion 2 (spec modified): revert the spec with
`git checkout HEAD -- *.spec` in {{ local_clone }}, verify the revert with
`git diff HEAD -- *.spec`, then rebuild (step 6).
`git diff HEAD -- *.spec`, then regenerate the SRPM (step 6).
- Criterion 4 (SRPM missing): re-run `build_srpm` (step 6).
- Criterion 5 (wrong patch name): rename the file to match the spec, then
rebuild (step 6).
regenerate the SRPM (step 6).

After fixing, re-run this self-review before reporting.

Expand All @@ -251,7 +251,8 @@ SPECIAL CONSIDERATIONS FOR TEST FAILURES:

List only the failing criteria. Do not mention passing ones.

Report success=true with SRPM path if build passes.
Report success=false with the extracted error if build fails or you can't find a fix.
Report success=true with the SRPM path once your patches apply cleanly and the
SRPM has been regenerated.
Report success=false with the problem you found if you cannot produce a fix.

Unpacked upstream sources are in {{ unpacked_sources }}.
13 changes: 7 additions & 6 deletions ymir/agents/prompts/build/instructions.j2
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ Do not submit or retry a build, regenerate an SRPM, or change package sources or
Your only task is to explain this existing failure using the supplied result and logs.

{% if has_extract_log_snippets %}
Download the *.log.gz files from the supplied `artifacts_urls` using the
Download every log referenced by the supplied `artifacts_urls` using the
`download_artifacts` tool to a temporary directory.
Use the `extract_log_snippets` tool with `log_path` pointing to the location of
`builder-live.log` and try to identify the build failure. The downloaded files are only
Inspect each downloaded log file with the `extract_log_snippets` tool, passing its
path as `log_path`; start with the main build log and work through the rest until you
identify the build failure. The downloaded files are only
accessible through `extract_log_snippets` — never use `view`, `search_text`, or shell
commands to read them, even after a successful `extract_log_snippets` call; those tools
run in a different sandbox and will always fail with "No such file or directory" on
Expand All @@ -18,12 +19,12 @@ Retrieve the *.log.gz files directly from the supplied `artifacts_urls` into a
temporary directory in your local sandbox using shell commands (for example,
curl or wget), then decompress and inspect them with the local tools.
Start with `builder-live.log` and try to identify the build failure.
If the failure is not found there, try the same with `root.log`.
{% endif %}
If there are no accessible logs, return the supplied error message and explain that
a detailed diagnosis was unavailable.
If the failure is not found, try the same with `root.log`. Summarize the findings
and return them as `error`. Build status and retry decisions belong to the workflow,
not to this analysis.
Summarize the findings and return them as `error`. Build status and retry decisions
belong to the workflow, not to this analysis.

General instructions:

Expand Down
26 changes: 26 additions & 0 deletions ymir/agents/rebase_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,31 @@ async def find_consolidated_siblings(state):
logger.info(
f"Using {len(state.consolidated_issues)} consolidated siblings from triage result"
)
return "close_stale_merge_requests"

async def close_stale_merge_requests(state):
# A rerun re-pushes the update branch; a lingering open MR would
# re-trigger GitLab CI (scratch builds) on every push and waste
# resources. Close it first so only the rerun's fresh MR runs CI.
# MR writes are suppressed under dry-run.
if not dry_run:
try:
closed = await tasks.close_stale_update_merge_requests(
jira_issue=state.jira_issue,
package=state.package,
dist_git_branch=state.dist_git_branch,
available_tools=gateway_tools,
dist_git_namespace=state.dist_git_namespace,
)
if closed:
logger.info(
"Closed %d stale MR(s) for %s: %s",
len(closed),
state.jira_issue,
closed,
)
except Exception as e:
logger.warning("Failed to close stale MRs for %s: %s", state.jira_issue, e)
return "fork_and_prepare_dist_git"

async def fork_and_prepare_dist_git(state):
Expand Down Expand Up @@ -680,6 +705,7 @@ async def comment_in_jira(state):
workflow.add_step("check_if_sibling", check_if_sibling)
workflow.add_step("change_jira_status", change_jira_status)
workflow.add_step("find_consolidated_siblings", find_consolidated_siblings)
workflow.add_step("close_stale_merge_requests", close_stale_merge_requests)
workflow.add_step("fork_and_prepare_dist_git", fork_and_prepare_dist_git)
workflow.add_step("run_rebase_agent", run_rebase_agent)
workflow.add_step("run_build_agent", run_build_agent)
Expand Down
Loading
Loading