Skip to content

feat: support CI artifact context injection and workflow_run trigger#2494

Open
MahmoudHaouachi wants to merge 2 commits into
The-PR-Agent:mainfrom
idealo:feat/ci-artifact-context
Open

feat: support CI artifact context injection and workflow_run trigger#2494
MahmoudHaouachi wants to merge 2 commits into
The-PR-Agent:mainfrom
idealo:feat/ci-artifact-context

Conversation

@MahmoudHaouachi

@MahmoudHaouachi MahmoudHaouachi commented Jul 2, 2026

Copy link
Copy Markdown

Closes #2493

Summary

Adds two related enhancements that together enable a common CI/CD pattern: running PR-Agent after a prior workflow completes, with that workflow's artifacts injected as extra review context.

Changes

1. pr_agent/algo/artifacts.py (new)

Reads a local artifact file and formats its content for injection into tool prompts.

  • resolve_artifact_path — resolves relative/absolute paths, confined to GITHUB_WORKSPACE (rejects traversal outside workspace)
  • _read_and_truncate — reads up to max_artifact_size characters without loading the full file into memory
  • format_artifact_content(content, label, instructions) — wraps content with a header and instruction text (uses DEFAULT_ARTIFACT_INSTRUCTIONS if none provided)
  • load_artifact() — orchestrates the above; no-op unless enabled and artifact_path is set

2. pr_agent/servers/github_action_runner.py

Artifact injection block: reads ARTIFACT_PATH / ARTIFACT_INSTRUCTIONS env vars before event dispatch, calls load_artifact() once, and appends the result to extra_instructions for the configured target tools. Safe no-op when ARTIFACT_PATH is not set.

workflow_run handler: new elif GITHUB_EVENT_NAME == "workflow_run": branch. Extracts the PR URL from workflow_run.pull_requests[0].url and runs the same auto tools as the pull_request handler. Guards: skips non-pull_request origins and empty pull_requests arrays (fork PRs).

3. action.yaml

Two new optional inputs with safe defaults:

  • artifact_path (default "") — path to artifact relative to GITHUB_WORKSPACE
  • artifact_instructions (default "") — custom instructions telling the AI how to interpret the artifact; defaults to a sensible generic prompt when left empty

4. pr_agent/settings/configuration.toml

New [artifacts] section with all options (enable, artifact_path, artifact_instructions, artifact_label, target_tools, max_artifact_size).

5. Tests

  • tests/unittest/test_artifacts.py (new, 21 tests) — covers path resolution, truncation, formatting, load gating, and security (path traversal rejection)
  • tests/unittest/test_github_action_runner_core.py — 3 new tests for workflow_run: runs tools, skips non-PR origin, skips empty pull_requests

6. github_action/entrypoint.sh

Added set -e for fail-fast behavior.


Example: terraform plan review via workflow_run

on:
  workflow_run:
    workflows: ["Terraform Plan"]
    types: [completed]

jobs:
  pr-agent:
    if: github.event.workflow_run.event == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - name: Download terraform plan
        uses: actions/download-artifact@v4
        with:
          run-id: ${{ github.event.workflow_run.id }}
          name: terraform-plan
          path: plans

      - name: Review PR with plan context
        uses: The-PR-Agent/pr-agent@main
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          artifact_path: plans/plan.txt
          artifact_instructions: >
            This is the Terraform execution plan for the infrastructure changes in this PR.
            Verify that the code changes produce the intended modifications.
            Flag any unexpected resource deletions or risky changes.

Toggle / opt-out

  • Default: completely off — artifact_path defaults to "", injection never runs
  • Enable: set artifact_path in action inputs, or [artifacts] enable = true + artifact_path in .pr_agent.toml
  • Disable: remove artifact_path from action inputs, or [artifacts] enable = false
  • The workflow_run handler only fires when GITHUB_EVENT_NAME=workflow_run — existing pull_request and issue_comment flows are completely unchanged

@github-actions github-actions Bot added the feature 💡 label Jul 2, 2026
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Support workflow_run trigger and inject CI artifact context into PR tools

✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add registry-based artifact parsers to inject CI outputs into PR-Agent prompts.
• Extend GitHub Action runner to support workflow_run events and run auto tools.
• Expose artifact path/type as action inputs and document artifacts configuration defaults.
Diagram

graph TD
  A{{"GitHub event"}} --> B["github_action_runner.py"] --> C["Dynaconf settings"] --> D["artifacts.py"] --> E[("Artifact file")]
  B --> F["PR tools"] --> G["GitHub PR API"]
  C --> F

  subgraph Legend
    direction LR
    _evt{{"Event"}} ~~~ _proc["Process/Module"] ~~~ _file[("File")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Fetch artifacts via GitHub API instead of local file
  • ➕ No dependency on workspace file layout or download-artifact step
  • ➕ Could support larger/multiple artifacts with pagination
  • ➖ Requires additional API calls/permissions and artifact lookup logic
  • ➖ More complexity and failure modes than local file injection
2. Support multiple artifacts as a list input
  • ➕ Better matches real CI runs (plan + test report + lints)
  • ➕ Avoids overloading a single artifact with mixed content
  • ➖ Needs prompt-size budgeting and ordering/labeling strategy
  • ➖ Expands configuration/testing surface area
3. Attach artifact context as a PR comment link/summary instead of prompt injection
  • ➕ Keeps model prompts smaller and more predictable
  • ➕ Makes artifact context visible/auditable in GitHub UI
  • ➖ Less direct for model reasoning unless comments are always fetched
  • ➖ Potentially noisy PR timeline depending on frequency

Recommendation: The PR’s approach (opt-in local file injection + parser registry) is the right default: it’s simple, safe (no-op when unset), and keeps permissions minimal. Consider multi-artifact support as a future extension once prompt-size limits and prioritization rules are defined.

Files changed (7) +507 / -0

Enhancement (2) +209 / -0
artifacts.pyAdd artifact parsing/formatting module with registry and truncation +132/-0

Add artifact parsing/formatting module with registry and truncation

• Implements artifact path resolution (absolute, workspace-relative, or CWD) and safe file reading with size truncation. Adds a parser registry with built-in generic, terraform_plan, and test_report formatters and a load_artifact_content() gate controlled by settings.

pr_agent/algo/artifacts.py

github_action_runner.pyInject artifact context and add workflow_run event handling +77/-0

Inject artifact context and add workflow_run event handling

• Adds an opt-in artifact injection block that enables ARTIFACTS settings from env vars and appends parsed artifact text to extra_instructions for selected tools. Adds a workflow_run handler that extracts the PR URL from the payload, applies repo settings, and runs the same auto tools with guards for non-PR origins and empty pull_requests arrays.

pr_agent/servers/github_action_runner.py

Tests (2) +271 / -0
test_artifacts.pyAdd unit tests for artifact resolution, parsing, and load gating +176/-0

Add unit tests for artifact resolution, parsing, and load gating

• Covers path resolution behaviors (workspace vs CWD), file truncation, parser registry contents, and load_artifact_content() gating (disabled/missing path/unknown type). Verifies terraform_plan parsing and generic fallback behavior.

tests/unittest/test_artifacts.py

test_github_action_runner_core.pyAdd workflow_run dispatch tests and skip-guard coverage +95/-0

Add workflow_run dispatch tests and skip-guard coverage

• Adds tests ensuring workflow_run triggers the expected auto tools when originating from pull_request. Verifies the runner skips execution for non-pull_request origins and when the payload has no pull_requests entries.

tests/unittest/test_github_action_runner_core.py

Other (3) +27 / -0
action.yamlAdd artifact_path/type inputs and map them to runner env vars +12/-0

Add artifact_path/type inputs and map them to runner env vars

• Introduces optional action inputs for artifact_path and artifact_type with safe defaults. Exposes them as ARTIFACT_PATH/ARTIFACT_TYPE environment variables for the container runner.

action.yaml

entrypoint.shFail fast in GitHub Action entrypoint +1/-0

Fail fast in GitHub Action entrypoint

• Adds 'set -e' so the action exits on the first failing command instead of continuing silently.

github_action/entrypoint.sh

configuration.tomlDocument artifacts configuration section and defaults +14/-0

Document artifacts configuration section and defaults

• Adds an [artifacts] section documenting enable/path/type/label/target_tools and max_artifact_size, with injection off by default.

pr_agent/settings/configuration.toml

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (5) 📘 Rule violations (2) 📎 Requirement gaps (1) 📜 Skill insights (0)

Context used

Grey Divider


Action required

1. Truncation marker can exceed limit 📎 Requirement gap ➹ Performance
Description
_read_and_truncate() can return a string longer than max_artifact_size when max_size is
smaller than the truncation marker length, violating the strict size bound required for prompt
injection controls. This can unexpectedly bloat injected artifact content and prompts despite a
small configured limit.
Code

pr_agent/algo/artifacts.py[R57-59]

+    if len(content) > max_size:
+        content = content[:max_size - len(_TRUNCATION_MARKER)] + _TRUNCATION_MARKER
+    return content
Evidence
Compliance (PR Compliance ID 8) requires enforcing max_artifact_size as a strict upper bound, but
the truncation logic slices using max_size - len(_TRUNCATION_MARKER) and then appends the full
_TRUNCATION_MARKER regardless of how small max_size is. When that subtraction is negative (i.e.,
max_size < len(_TRUNCATION_MARKER)), the slice end becomes negative and the function can
effectively return the marker alone, making the result length exceed max_size and breaking the
guarantee.

Artifact injection respects max_artifact_size and truncates safely
pr_agent/algo/artifacts.py[46-59]

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

## Issue description
`_read_and_truncate()` tries to keep output within `max_size` by slicing to `max_size - len(_TRUNCATION_MARKER)` and then appending `_TRUNCATION_MARKER`. When `max_size < len(_TRUNCATION_MARKER)`, the slice end becomes negative and the function can return output (e.g., the marker alone) that is **longer than `max_size`**, violating the strict `max_artifact_size` bound required for prompt-injection safety.
## Issue Context
Compliance requires injected artifact text to respect `max_artifact_size` under all configurations, including very small user-configurable values. The fix should ensure the returned string length is always `<= max_size` even when the truncation marker itself would exceed the limit.
## Fix Focus Areas
- pr_agent/algo/artifacts.py[46-59]
### Implementation notes
- Compute `available = max_size - len(_TRUNCATION_MARKER)`.
- If `available <= 0`, return `_TRUNCATION_MARKER[:max_size]` (or return an empty string) so the result length is always `<= max_size`.
- Otherwise keep current behavior: `content = content[:available] + _TRUNCATION_MARKER`.
- Add/adjust a unit test for the small-limit case (e.g., `max_size=10`).

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


2. Workflow_run tests leak settings ✓ Resolved 🐞 Bug ☼ Reliability
Description
The new workflow_run unit tests call run_action(), which mutates global Dynaconf flags
(config.is_auto_command and pr_description.final_update_message), but the tests don’t restore those
flags afterward. This can leak state into later tests and make the overall test suite
order-dependent or flaky.
Code

tests/unittest/test_github_action_runner_core.py[R233-283]

+@pytest.mark.asyncio
+async def test_workflow_run_runs_auto_tools(monkeypatch, tmp_path, restore_github_settings):
+    runs = []
+    _patch_workflow_run_deps(monkeypatch, runs)
+    monkeypatch.setenv("GITHUB_EVENT_NAME", "workflow_run")
+    monkeypatch.setenv("GITHUB_EVENT_PATH", str(_write_workflow_run_event(tmp_path)))
+    monkeypatch.setenv("GITHUB_TOKEN", "token")
+
+    def fake_get_setting_or_env(key, default=None):
+        values = {
+            "GITHUB_ACTION.AUTO_DESCRIBE": True,
+            "GITHUB_ACTION.AUTO_REVIEW": True,
+            "GITHUB_ACTION.AUTO_IMPROVE": False,
+            "GITHUB_ACTION_CONFIG.ENABLE_OUTPUT": True,
+        }
+        return values.get(key, default)
+
+    monkeypatch.setattr(github_action_runner, "get_setting_or_env", fake_get_setting_or_env)
+
+    await github_action_runner.run_action()
+
+    assert runs == [
+        ("describe", "https://api.github.com/repos/org/repo/pulls/42"),
+        ("review", "https://api.github.com/repos/org/repo/pulls/42"),
+    ]
+
+
+@pytest.mark.asyncio
+async def test_workflow_run_skips_non_pull_request_origin(monkeypatch, tmp_path, restore_github_settings):
+    runs = []
+    _patch_workflow_run_deps(monkeypatch, runs)
+    monkeypatch.setenv("GITHUB_EVENT_NAME", "workflow_run")
+    monkeypatch.setenv("GITHUB_EVENT_PATH", str(_write_workflow_run_event(tmp_path, originating_event="push")))
+    monkeypatch.setenv("GITHUB_TOKEN", "token")
+
+    await github_action_runner.run_action()
+
+    assert runs == []
+
+
+@pytest.mark.asyncio
+async def test_workflow_run_skips_when_pull_requests_empty(monkeypatch, tmp_path, restore_github_settings):
+    runs = []
+    _patch_workflow_run_deps(monkeypatch, runs)
+    monkeypatch.setenv("GITHUB_EVENT_NAME", "workflow_run")
+    monkeypatch.setenv("GITHUB_EVENT_PATH", str(_write_workflow_run_event(tmp_path, pull_requests=[])))
+    monkeypatch.setenv("GITHUB_TOKEN", "token")
+
+    await github_action_runner.run_action()
+
+    assert runs == []
Evidence
run_action() sets global flags in the new workflow_run branch, and the added tests invoke it
without restoring those flags; the existing restore fixture only resets
GITHUB/GITHUB_ACTION_CONFIG, so these mutations can persist across tests.

pr_agent/servers/github_action_runner.py[242-289]
tests/unittest/test_github_action_runner_core.py[108-126]
tests/unittest/test_github_action_runner_core.py[233-283]

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 new `workflow_run` tests invoke `github_action_runner.run_action()`, which mutates global settings (`settings.config.is_auto_command`, `settings.pr_description.final_update_message`). The tests only use `restore_github_settings`, which restores `GITHUB` / `GITHUB_ACTION_CONFIG` but not these mutated flags, causing cross-test state leakage.
## Issue Context
`run_action()` is designed for a one-shot GitHub Action process, so it doesn't reset global settings at the end. In unit tests, however, the same process runs many tests, so global settings must be restored to keep tests isolated.
## Fix Focus Areas
- tests/unittest/test_github_action_runner_core.py[108-126]
- tests/unittest/test_github_action_runner_core.py[233-283]
- pr_agent/servers/github_action_runner.py[280-289]
## What to change
- Option A (preferred): extend the `restore_github_settings` fixture to also snapshot+restore:
- `settings.config.is_auto_command`
- `settings.pr_description.final_update_message`
- (optionally) `settings.config.response_language` (for symmetry with the existing pull_request test)
- Option B: in each new workflow_run test, follow the pattern used in `test_run_action_invokes_enabled_auto_tools_for_pull_request_event` (try/finally restoring the mutated flags).

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


3. Broad except Exception in _inject_artifact_context() ✓ Resolved 📘 Rule violation ⛨ Security
Description
_inject_artifact_context() uses a broad except Exception and continues execution, which can mask
unexpected failures in a security-relevant config/file-loading path. This reduces visibility into
malformed configuration or unexpected runtime conditions.
Code

pr_agent/servers/github_action_runner.py[R76-77]

+    except Exception as e:
+        get_logger().warning(f"github action: failed to process artifacts: {e}", exc_info=True)
Evidence
PR Compliance ID 28 requires avoiding broad exception masking in security-relevant
configuration/logging paths. The new _inject_artifact_context() catches Exception for the entire
injection flow and only logs a warning, which can hide unexpected errors.

pr_agent/servers/github_action_runner.py[38-77]
Best Practice: Learned patterns

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

## Issue description
`_inject_artifact_context()` wraps its full logic in `except Exception`, which can unintentionally mask programming errors and security-relevant failures during config/env parsing and artifact loading.
## Issue Context
Artifact injection reads local files and mutates tool prompt settings, so failures should be handled with targeted exception handling and clear behavior.
## Fix Focus Areas
- pr_agent/servers/github_action_runner.py[38-77]

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


View more (2)
4. Injection lost after repo settings ✓ Resolved 🐞 Bug ≡ Correctness
Description
In workflow_run events, run_action() injects artifact context before the PR URL is known, then
apply_repo_settings(pr_url) can overwrite tool sections (including extra_instructions), and the
later workflow_run injection attempt is skipped due to the ARTIFACTS._INJECTED guard. This can
silently drop artifact context from prompts in the workflow_run flow.
Code

pr_agent/servers/github_action_runner.py[R157-159]

+    # Inject artifact context (for pull_request events; repo settings already applied above)
+    _inject_artifact_context()
+
Evidence
The artifact injection is invoked unconditionally before event handling, and
_inject_artifact_context() sets an _INJECTED flag that prevents re-running. In workflow_run,
repo settings are applied after extracting pr_url, and apply_repo_settings() overwrites entire
settings sections, which can remove previously appended extra_instructions, leaving no chance to
re-inject due to the guard.

pr_agent/servers/github_action_runner.py[33-37]
pr_agent/servers/github_action_runner.py[157-159]
pr_agent/servers/github_action_runner.py[243-270]
pr_agent/git_providers/utils.py[306-316]

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

## Issue description
`_inject_artifact_context()` is called unconditionally before event dispatch. For `workflow_run`, repo settings are applied later (once the PR URL is extracted), and `apply_repo_settings()` can replace tool sections (including `extra_instructions`), effectively removing the previously injected artifact context. Because `_inject_artifact_context()` sets `ARTIFACTS._INJECTED`, the later call in the `workflow_run` branch becomes a no-op, so artifact context never gets re-applied.
### Issue Context
This primarily impacts the new `workflow_run` trigger, where the PR URL is not available when the early injection runs.
### Fix Focus Areas
- pr_agent/servers/github_action_runner.py[33-37]
- pr_agent/servers/github_action_runner.py[157-159]
- pr_agent/servers/github_action_runner.py[243-270]
- pr_agent/git_providers/utils.py[306-316]
### Suggested fix approach
- Do **not** call `_inject_artifact_context()` before event dispatch for all events.
- Instead, call it only in branches where repo settings are already applied:
- `pull_request`/`pull_request_target`: after the existing `apply_repo_settings(pr_url)` block.
- `workflow_run`: after `apply_repo_settings(pr_url)` in the `workflow_run` branch.
- Alternatively, make `_inject_artifact_context()` resilient to repo settings reloads by:
- delaying setting `ARTIFACTS._INJECTED` until after repo settings are finalized, or
- re-injecting when repo settings were applied after the first injection (e.g., reset `_INJECTED` after `apply_repo_settings` for workflow_run), while ensuring it still remains idempotent per run.

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


5. Truncation exceeds max_artifact_size ✓ Resolved 📎 Requirement gap ➹ Performance
Description
_read_and_truncate() truncates to max_size but then appends a truncation marker, causing
injected artifact content to exceed the configured maximum. This undermines the compliance goal of
keeping injected artifact content within a strict size bound to protect prompt size and performance.
Code

pr_agent/algo/artifacts.py[R52-54]

+    if len(content) > max_size:
+        content = content[:max_size] + "\n\n[... content truncated due to size limit ...]"
+    return content
Evidence
PR Compliance ID 10 requires artifact content to be truncated to at most the configured maximum size
before injection. The code truncates to content[:max_size] but then appends an additional marker
string, making the returned value longer than max_size.

Enforce max artifact size with truncation
pr_agent/algo/artifacts.py[52-54]

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

## Issue description
`_read_and_truncate()` is intended to enforce a maximum artifact size (characters) before the artifact is injected into tool `extra_instructions`. However, when truncation happens it slices the content to `max_size` and then appends a truncation marker, making the returned string longer than `max_size`.
## Issue Context
This violates the requirement that artifact content be truncated to at most the configured maximum size before injection.
## Fix Focus Areas
- pr_agent/algo/artifacts.py[52-54]

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



Remediation recommended

6. _inject_artifact_context uses single quotes 📘 Rule violation ⚙ Maintainability
Description
New/modified Python code introduces several single-quoted string literals in both
_inject_artifact_context() and an updated test fixture, violating the repository requirement (PR
Compliance ID 18) to prefer double quotes. This can lead to avoidable lint churn, inconsistent
style, and potential Ruff/style failures.
Code

pr_agent/servers/github_action_runner.py[R33-40]

+def _inject_artifact_context():
+    """Inject CI artifact content into extra_instructions for configured tools."""
+    artifact_path_env = (
+        os.environ.get('ARTIFACT_PATH') or os.environ.get('PR_AGENT_ARTIFACT_PATH') or ''
+    ).strip()
+    artifact_instructions_env = (
+        os.environ.get('ARTIFACT_INSTRUCTIONS') or os.environ.get('PR_AGENT_ARTIFACT_INSTRUCTIONS') or ''
+    ).strip()
Evidence
PR Compliance ID 18 specifies that new/modified Python code should prefer double-quoted strings. The
cited changes show single-quoted string literals being used: in
pr_agent/servers/github_action_runner.py within _inject_artifact_context() for environment
variable keys/defaults, and in tests/unittest/test_github_action_runner_core.py within
getattr(..., 'is_auto_command', ...) and getattr(..., 'final_update_message', ...),
demonstrating noncompliance with the required quoting convention.

AGENTS.md: Ruff/Import/String Style Must Match Repository Configuration (Line Length 120, isort Grouping, Prefer Double Quotes): AGENTS.md: Ruff/Import/String Style Must Match Repository Configuration (Line Length 120, isort Grouping, Prefer Double Quotes): AGENTS.md: Ruff/Import/String Style Must Match Repository Configuration (Line Length 120, isort Grouping, Prefer Double Quotes): AGENTS.md: Ruff/Import/String Style Must Match Repository Configuration (Line Length 120, isort Grouping, Prefer Double Quotes)
pr_agent/servers/github_action_runner.py[33-40]
tests/unittest/test_github_action_runner_core.py[119-120]

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 repository style (PR Compliance ID 18) requires preferring double quotes in new/modified Python code, but recent changes introduced single-quoted string literals in `_inject_artifact_context()` and in a test fixture (including `getattr(..., 'is_auto_command', ...)` and `getattr(..., 'final_update_message', ...)`). Update these new/modified string literals to use double quotes to align with the enforced style and avoid potential lint/style failures.
## Issue Context
The compliance checklist enforces consistent Python string quoting conventions (often via Ruff or related style configuration). The current changes add single-quoted strings for environment variable keys/defaults in `_inject_artifact_context()` and for string arguments inside `getattr(...)` in the unit test, which conflicts with the preferred double-quote convention for new/modified code.
## Fix Focus Areas
- pr_agent/servers/github_action_runner.py[33-40]
- tests/unittest/test_github_action_runner_core.py[119-120]

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


7. Root workspace rejected 🐞 Bug ≡ Correctness
Description
resolve_artifact_path() uses str(resolved).startswith(str(workspace_resolved) + os.sep) to
enforce “under workspace”; when GITHUB_WORKSPACE resolves to /, the prefix becomes // and
almost all absolute paths are incorrectly rejected, disabling artifact injection in that
environment.
Code

pr_agent/algo/artifacts.py[R28-33]

+        if workspace:
+            workspace_resolved = Path(workspace).resolve()
+            under_workspace = (
+                str(resolved).startswith(str(workspace_resolved) + os.sep)
+                or resolved == workspace_resolved
+            )
Evidence
The workspace check is implemented with a string prefix of workspace_resolved + os.sep; when
workspace_resolved is /, that prefix becomes //, which does not match normal absolute paths
(e.g. /tmp/...), so under_workspace becomes false and the function returns None (rejecting the
artifact path).

pr_agent/algo/artifacts.py[28-40]

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

## Issue description
`resolve_artifact_path()` enforces that the resolved artifact path is under `GITHUB_WORKSPACE` via a string-prefix check: `startswith(str(workspace_resolved) + os.sep)`. If `GITHUB_WORKSPACE` is `/`, this becomes `startswith("//")` and rejects valid paths like `/tmp/x`, causing artifact injection to silently no-op.
## Issue Context
This impacts environments where `GITHUB_WORKSPACE` resolves to the root directory.
## Fix Focus Areas
- pr_agent/algo/artifacts.py[28-38]
### Suggested approach
Replace the string-prefix logic with a path-aware containment check, e.g.:
- `under_workspace = (resolved == workspace_resolved) or (workspace_resolved in resolved.parents)`
- or `resolved.relative_to(workspace_resolved)` in a try/except (Python 3.9+ also has `Path.is_relative_to`).

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


8. Artifacts injected on issues 🐞 Bug ≡ Correctness
Description
In the issue_comment/pull_request_review_comment handler, _inject_artifact_context() is called
whenever an issue URL is present, even when the comment is on a non-PR issue. This pollutes prompts
for issue-triggered commands with unrelated CI artifact content and can cause responses to
incorporate artifact data in non-PR contexts.
Code

pr_agent/servers/github_action_runner.py[R273-279]

                url = event_payload.get("issue", {}).get("url")
            if url:
+                    _inject_artifact_context()
                body = comment_body.strip().lower()
                comment_id = event_payload.get("comment", {}).get("id")
                provider = get_git_provider()(pr_url=url)
Evidence
The code calls _inject_artifact_context() immediately after if url: and before checking `if
is_pr:`, so non-PR issues that still have an issue URL will also trigger artifact injection.

pr_agent/servers/github_action_runner.py[261-288]

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

## Issue description
`run_action()` injects artifact context for `issue_comment` events before it branches on whether the comment targets a PR vs a regular issue. As a result, artifact context can be added even for non-PR issues.
### Issue Context
The handler already computes `is_pr` based on `issue.pull_request` / `comment.pull_request_url`. Artifact injection should be gated on `is_pr` (or otherwise only occur for PR flows).
### Fix Focus Areas
- pr_agent/servers/github_action_runner.py[261-288]
### Implementation notes
- Move `_inject_artifact_context()` inside the `if is_pr:` branch (or gate it with `if is_pr and url:`).
- Keep current behavior for PR comments unchanged.
- Add a unit test ensuring `_inject_artifact_context()` is *not* called for an `issue_comment` payload that lacks `issue.pull_request` and lacks `comment.pull_request_url`.

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


View more (3)
9. apply_repo_settings() broad exception 📘 Rule violation ⛨ Security
Description
The new workflow_run handler catches Exception around apply_repo_settings(pr_url) and
continues, which can silently mask unexpected failures in a configuration-loading path. This reduces
visibility into misconfiguration and can lead to confusing or insecure behavior when repo settings
fail to apply.
Code

pr_agent/servers/github_action_runner.py[R309-313]

+        try:
+            apply_repo_settings(pr_url)
+        except Exception as e:
+            get_logger().info(f"github action: failed to apply repo settings for workflow_run: {e}")
+
Evidence
PR Compliance ID 31 requires avoiding broad exception masking in security-relevant configuration
paths. The added except Exception around apply_repo_settings(pr_url) in the workflow_run
handler masks all errors and only logs an info message, allowing execution to proceed without
confirmed settings application.

pr_agent/servers/github_action_runner.py[309-313]
Best Practice: Learned patterns

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

## Issue description
In the new `workflow_run` branch, `apply_repo_settings(pr_url)` is wrapped with a broad `except Exception`, which can mask unexpected failures in a configuration/security boundary.
## Issue Context
This code path controls loading repo-specific settings before running auto tools, so failures should be handled explicitly (narrow exceptions, log with enough context, and consider failing fast or returning).
## Fix Focus Areas
- pr_agent/servers/github_action_runner.py[309-313]

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


10. Artifact config casing brittle 🐞 Bug ☼ Reliability
Description
load_artifact() reads the ARTIFACTS section as a mapping and looks up lowercase keys (enable,
artifact_path, etc.), while _inject_artifact_context() writes uppercase dotted keys
(ARTIFACTS.ENABLE, ARTIFACTS.ARTIFACT_PATH). This inconsistent casing makes artifact loading
depend on Dynaconf/boxing key-normalization and can result in artifact injection being skipped even
when the action inputs set ARTIFACT_PATH.
Code

pr_agent/algo/artifacts.py[R84-92]

+    enable = artifacts_settings.get("enable", False)
+    if isinstance(enable, str):
+        enable = enable.lower() == "true"
+    if not enable:
+        return ""
+
+    artifact_path_str = artifacts_settings.get("artifact_path", "")
+    if not artifact_path_str:
+        return ""
Evidence
The runner writes artifact settings using uppercase dotted keys, while load_artifact() reads from
the ARTIFACTS mapping using lowercase field names; the repo TOML loader preserves field names as
written (lowercase), reinforcing the expectation that keys are case-sensitive within section dicts.

pr_agent/servers/github_action_runner.py[33-46]
pr_agent/algo/artifacts.py[75-93]
pr_agent/settings/configuration.toml[398-410]
pr_agent/custom_merge_loader.py[83-102]
pr_agent/git_providers/utils.py[348-370]

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

## Issue description
`load_artifact()` reads `enable` / `artifact_path` from the `ARTIFACTS` mapping using lowercase keys, but the GitHub Action runner sets `ARTIFACTS.ENABLE` / `ARTIFACTS.ARTIFACT_PATH` (uppercase). This mismatch makes the feature fragile and may cause env-provided artifact settings not to be recognized.
## Issue Context
- Repo configuration TOML defines `[artifacts]` keys in lowercase.
- The GitHub Action runner sets artifact settings via `get_settings().set("ARTIFACTS.*")`.
## Fix Focus Areas
- pr_agent/algo/artifacts.py[75-114]
- pr_agent/servers/github_action_runner.py[33-48]
- pr_agent/settings/configuration.toml[398-410]
## Suggested change
Prefer one canonical access path:
- In `load_artifact()`, read via Dynaconf dotted keys consistently:
- `enable = get_settings().get("ARTIFACTS.ENABLE", False)`
- `artifact_path_str = get_settings().get("ARTIFACTS.ARTIFACT_PATH", "")`
- similarly for `MAX_ARTIFACT_SIZE`, `ARTIFACT_LABEL`, `ARTIFACT_INSTRUCTIONS`
Optionally add backward-compatibility by checking both cases if you keep mapping-based reads.

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


11. Artifact skipped on comments 🐞 Bug ≡ Correctness
Description
run_action() only calls _inject_artifact_context() for pull_request* and workflow_run
events, so issue_comment / pull_request_review_comment triggered runs never receive the artifact
text even when artifacts are enabled. This makes the new artifact inputs/config ineffective for
manual comment-triggered tools (e.g., /review) and produces inconsistent behavior across triggers.
Code

pr_agent/servers/github_action_runner.py[R157-160]

# Handle pull request opened event
if GITHUB_EVENT_NAME == "pull_request" or GITHUB_EVENT_NAME == "pull_request_target":
+        # Inject artifact context here so it runs after apply_repo_settings above
+        _inject_artifact_context()
Evidence
The code injects artifacts for PR open/sync flows and for workflow_run, but the comment-handling
branch never calls _inject_artifact_context(), so comment-triggered executions can’t pick up the
new artifact inputs/config.

pr_agent/servers/github_action_runner.py[157-160]
pr_agent/servers/github_action_runner.py[241-287]
pr_agent/servers/github_action_runner.py[288-315]
action.yaml[6-20]

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

## Issue description
Artifact context injection (`_inject_artifact_context()`) is only invoked in the `pull_request*` and `workflow_run` paths. As a result, when the action is triggered by `issue_comment` / `pull_request_review_comment`, any configured `ARTIFACT_PATH` / `[artifacts]` settings are ignored and the artifact is not appended to tool `extra_instructions`.
### Issue Context
This PR introduces artifact injection as general extra context for PR analysis, but comment-triggered commands are a major execution path for PR-Agent.
### Fix Focus Areas
- pr_agent/servers/github_action_runner.py[157-160]
- pr_agent/servers/github_action_runner.py[241-287]
### Proposed fix
In the `issue_comment` / `pull_request_review_comment` branch, call `_inject_artifact_context()` before invoking `PRAgent().handle_request(...)` so that manual commands (e.g., `/review`, `/improve`, `/describe`) also receive the artifact context when enabled.
(Optionally, if you want repo-specific settings to affect artifact injection for comment events, also apply repo settings for the resolved PR URL before injecting.)

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



Informational

12. Missing INPUT_* fallback 🐞 Bug ☼ Reliability ⭐ New
Description
_inject_artifact_context() only reads ARTIFACT_PATH/ARTIFACT_INSTRUCTIONS (and PR_AGENT_*
variants), so the feature depends on those env vars being present and ignores the standard GitHub
Actions INPUT_ARTIFACT_PATH/INPUT_ARTIFACT_INSTRUCTIONS variables. This can make artifact
injection silently not activate in execution modes where only INPUT_* variables are populated (or
if the action metadata env-mapping changes later).
Code

pr_agent/servers/github_action_runner.py[R35-40]

+    artifact_path_env = (
+        os.environ.get("ARTIFACT_PATH") or os.environ.get("PR_AGENT_ARTIFACT_PATH") or ""
+    ).strip()
+    artifact_instructions_env = (
+        os.environ.get("ARTIFACT_INSTRUCTIONS") or os.environ.get("PR_AGENT_ARTIFACT_INSTRUCTIONS") or ""
+    ).strip()
Evidence
The action introduces inputs, but the runtime injection code only consults ARTIFACT_PATH /
ARTIFACT_INSTRUCTIONS and does not look for any INPUT_* variables.

action.yaml[6-20]
pr_agent/servers/github_action_runner.py[33-40]

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

## Issue description
`_inject_artifact_context()` currently checks only `ARTIFACT_PATH` / `ARTIFACT_INSTRUCTIONS` (plus `PR_AGENT_*` aliases). GitHub Actions commonly exposes action inputs via `INPUT_<NAME>` environment variables, so supporting those as a fallback improves robustness and reduces reliance on action-metadata env wiring.

## Issue Context
- The action defines inputs `artifact_path` and `artifact_instructions`.
- The runner enables artifacts only when it sees the corresponding env vars.

## Fix Focus Areas
- pr_agent/servers/github_action_runner.py[33-46]
 - Extend env lookup to also consider `INPUT_ARTIFACT_PATH` and `INPUT_ARTIFACT_INSTRUCTIONS` (case-insensitive if you want), ideally as a fallback behind the explicit `ARTIFACT_*` env vars.
 - Keep current behavior unchanged when `ARTIFACT_*` is already provided.

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


Grey Divider

  • Author self-review: I have reviewed the code review findings, and addressed the relevant ones.

Qodo Logo

Comment thread pr_agent/algo/artifacts.py
@naorpeled

Copy link
Copy Markdown
Member

Hey @MahmoudHaouachi,
First of all, thanks for this. WDYT that instead of adding specific artifact types, we would by default take the generic artifact prompt and allow overriding the prompt itself within the action?

Comment thread tests/unittest/test_artifacts.py Fixed
Comment thread tests/unittest/test_artifacts.py Fixed
Comment thread tests/unittest/test_artifacts.py Fixed
@MahmoudHaouachi

Copy link
Copy Markdown
Author

Hey @MahmoudHaouachi, First of all, thanks for this. WDYT that instead of adding specific artifact types, we would by default take the generic artifact prompt and allow overriding the prompt itself within the action?

Hi @naorpeled , yes this sounds good, much simpler and removes redundancy

Comment thread action.yaml
Comment thread pr_agent/algo/artifacts.py
Comment thread pr_agent/servers/github_action_runner.py Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 19a1c0a

Comment thread tests/unittest/test_artifacts.py Outdated
Comment thread pr_agent/algo/artifacts.py Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit f3ad8fb

@naorpeled

Copy link
Copy Markdown
Member

Hey @MahmoudHaouachi,
Let me know once you finish addressing all of Qodo's findings and then I'll do a final review 🙏

Comment thread tests/unittest/test_artifacts.py Fixed
Comment thread tests/unittest/test_artifacts.py Fixed
Comment thread pr_agent/algo/artifacts.py
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 2fb424d

@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit f2a6cd1

@MahmoudHaouachi

Copy link
Copy Markdown
Author

Hey @MahmoudHaouachi, Let me know once you finish addressing all of Qodo's findings and then I'll do a final review 🙏

Hello @naorpeled, I'm through with Qodo's findings 👍

@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit a5f8e15

Comment thread pr_agent/servers/github_action_runner.py Outdated
Comment thread pr_agent/servers/github_action_runner.py Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 8493525

Comment thread tests/unittest/test_github_action_runner_core.py
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 783cc86

@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit c0c2503

@MahmoudHaouachi MahmoudHaouachi force-pushed the feat/ci-artifact-context branch from c0c2503 to 0fd5e76 Compare July 7, 2026 18:23
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 0fd5e76

@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 6ece4d4

@MahmoudHaouachi MahmoudHaouachi force-pushed the feat/ci-artifact-context branch from 6ece4d4 to 6a35570 Compare July 7, 2026 18:54
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 6a35570

@MahmoudHaouachi MahmoudHaouachi force-pushed the feat/ci-artifact-context branch from 6a35570 to 5e9593a Compare July 7, 2026 19:13
Comment thread pr_agent/algo/artifacts.py
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 5e9593a

Add support for injecting CI-produced artifact content (test reports, terraform
plans, build logs) into PR-Agent tool prompts via extra_instructions.

- New module pr_agent/algo/artifacts.py with load_artifact(), format_artifact_content(),
  resolve_artifact_path() (confined to GITHUB_WORKSPACE), _read_and_truncate()
- _inject_artifact_context() helper in github_action_runner.py called in all three
  dispatch paths (pull_request*, issue_comment, workflow_run)
- action.yaml: artifact_path + artifact_instructions inputs
- configuration.toml: [artifacts] section with enable, artifact_path, target_tools,
  max_artifact_size, artifact_instructions, artifact_label
- 44 unit tests covering artifacts module and runner integration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@MahmoudHaouachi MahmoudHaouachi force-pushed the feat/ci-artifact-context branch from 5e9593a to a378d84 Compare July 7, 2026 19:28
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit a378d84

- Use Path.is_relative_to() for workspace containment check (fixes root
  workspace edge case where GITHUB_WORKSPACE=/ produced "//" prefix)
- Gate _inject_artifact_context() on is_pr in issue_comment handler
- Upgrade log level for apply_repo_settings failure to warning
- Normalize single quotes to double quotes per repo style

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit bea1201

@MahmoudHaouachi

Copy link
Copy Markdown
Author

Hello @naorpeled , when can you merge this ? because we want to start using soon in my team

@naorpeled

Copy link
Copy Markdown
Member

Hello @naorpeled , when can you merge this ? because we want to start using soon in my team

Missed your previous comment, sorry, will try to review asap

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Support CI artifact context injection and workflow_run trigger for GitHub Action

3 participants