Skip to content

feat(#807): make code agent multi-forge (GitHub + GitLab) - #813

Open
fullsend-ai-coder[bot] wants to merge 1 commit into
mainfrom
agent/807-multi-forge-code
Open

feat(#807): make code agent multi-forge (GitHub + GitLab)#813
fullsend-ai-coder[bot] wants to merge 1 commit into
mainfrom
agent/807-multi-forge-code

Conversation

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor

Summary

Make the code agent support both GitHub and GitLab by introducing a forge-dispatch architecture, following the patterns established by the triage agent in PR #686.

Changes

  • Ops libraries: Created code-ops.lib.sh (forge dispatcher), github-code-ops.lib.sh (~20 functions using gh CLI), and gitlab-code-ops.lib.sh (~20 functions using curl against GitLab REST API)
  • Pre-script (pre-code.src.sh): Replaced GitHub-specific URL validation, existing-PR checks, and label/comment operations with forge-neutral forge_* function calls
  • Post-script (post-code.src.sh): Replaced all 25+ gh CLI calls with forge_* functions for push auth, PR/MR creation, auto-merge, assignee resolution, labels, comments, and CI URL generation
  • Harness (code.yaml): Added forge.gitlab section with policy, skills, host_files, and env vars matching the triage harness pattern
  • Policies: Moved existing policy to policies/github/code.yaml; created policies/gitlab/code.yaml with curl binary allowlist and GitLab API endpoints
  • Env files: Split into shared (env/code.env) + forge-specific (env/github/code.env, env/gitlab/code.env)
  • Agent prompt: Made forge-neutral (removed "GitHub" from description)

Design decisions

  • PR_NUMBER_FROM_URL naming convention preserved to avoid shellcheck SC2153 (per repo convention)
  • forge_list_prs_for_branch() propagates errors to maintain fail-closed security behavior
  • post-failure-report.lib.sh and pr-assignee.lib.sh left untouched for fix agent compatibility; GitLab ops remap response shapes to match GitHub expected format
  • GitLab auto-merge uses merge_when_pipeline_succeeds (no merge queue equivalent)
  • GitLab push auth uses oauth2:TOKEN format (not x-access-token)

Testing

  • All existing post-code-test.sh tests pass (including security integration tests)
  • All existing pre-code-test.sh tests pass (updated test env to include FULLSEND_FORGE=github)
  • shellcheck -x -e SC1091,SC2001,SC2016 passes on all source and bundled scripts
  • make check-bundle confirms bundled scripts match source
  • hack/lint-agent-docs passes

Closes #807

Post-script verification

  • Branch is not main/master (agent/807-multi-forge-code)
  • Secret scan passed (gitleaks — 693ed83bbd5289e1ef13d6c7a3772f61efc43c86..HEAD)
  • PR body secret scan passed (gitleaks — no-git)

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 14, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:21 PM UTC · Completed 9:40 PM UTC

Commit: b6f6f6a · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 14, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [protected-path] .github/scripts/select-eval-agents-test.sh, agents/code.md, harness/code.yaml, policies/github/code.yaml, policies/gitlab/code.yaml, scripts/lib/code-ops.lib.sh, scripts/lib/github-code-ops.lib.sh, scripts/lib/gitlab-code-ops.lib.sh, scripts/lib/post-failure-report.lib.sh, scripts/lib/pr-assignee.lib.sh, scripts/post-code-test.sh, scripts/post-code.sh, scripts/post-code.src.sh, scripts/post-fix.sh, scripts/pre-code-test.sh, scripts/pre-code.sh, scripts/pre-code.src.sh, skills/code-implementation/SKILL.md — 18 of 26 changed files are under protected paths (agents/, harness/, policies/, scripts/, skills/, .github/). The PR links to issue Make code agent multi-forge (GitHub + GitLab) #807 and provides detailed rationale for the multi-forge architecture following the triage agent pattern from PR feat: make triage agent multi-forge (GitHub + GitLab) #686. Human approval is required for protected-path changes regardless of context.

Low

  • [gitlab-host-bypass-via-env-override] harness/code.yamlGITLAB_HOST is injected into the sandbox via env.sandbox. The agent could theoretically tamper with it before API calls, but the network policy restricts curl to only gitlab.com and gitlab.cee.redhat.com, blocking connections to any other host. Defense-in-depth observation, not an exploitable gap. Consider removing GITLAB_HOST from env.sandbox if the agent does not need it directly.

  • [logic-error] scripts/lib/gitlab-code-ops.lib.sh:93 — forge_get_repo_merge_methods never sets squash (s) to true. GitLab controls squash via squash_option, not merge_method. forge_enable_auto_merge ignores the _method_flag parameter. However, enable_auto_merge in post-code.src.sh short-circuits for GitLab before reaching the merge-method resolution code, so CODE_AUTO_MERGE_METHOD has no effect on GitLab — the project-level merge_method setting determines the merge behavior.

  • [logic-error] scripts/lib/gitlab-code-ops.lib.sh:202 — forge_write_output is a no-op when GITHUB_OUTPUT is unset (normal on GitLab CI). The pr_url output is silently dropped. Documented as intentional.

  • [consumer-completeness] scripts/lib/gitlab-code-ops.lib.sh:142 — forge_list_prs_for_issue returns empty on API failure (|| true) rather than failing closed. Matches the identical GitHub implementation pattern.

  • [dead-code] scripts/post-fix.sh:271 — post-fix.sh does not source code-ops.lib.sh, so declare -F forge_* dispatch checks in its bundled post-failure-report.lib.sh copy are dead code. The fallback to gh CLI is correct.

  • [gitlab-token-scope-breadth] harness/code.yaml — The GitLab sandbox receives GITLAB_TOKEN with the full api scope, granting read and write access. Unlike GitHub where GH_TOKEN is explicitly scoped to read-only, GitLab has no equivalent scope restriction. Compensating controls: network policy (access: read-only) and binary restriction to curl.

  • [gha-log-masking-gap] scripts/post-code.src.sh::add-mask:: is applied to PUSH_TOKEN but not separately to GITLAB_TOKEN. In practice, GITLAB_TOKEN is set to PUSH_TOKEN at push time, so the value is covered by the existing mask. Defense-in-depth gap for future scenarios.

  • [sandbox-network-policy-broadening] policies/gitlab/code.yaml:49 — GitLab sandbox policy allows **/curl binary for gitlab_api network access. Architecturally necessary (no glab CLI equivalent). Restricted to gitlab.com and gitlab.cee.redhat.com endpoints with access: read-only.

  • [assignee-regex-relaxation] scripts/lib/pr-assignee.lib.sh:191 — Assignee format validation regex relaxed from ^[a-zA-Z0-9_-]+$ to ^[a-zA-Z0-9_.-]+$, adding dot for GitLab username compatibility. No injection risk from adding dots to the allowed character set.

  • [scope-gap] skills/code-implementation/SKILL.md — Issue Make code agent multi-forge (GitHub + GitLab) #807 Step 5 authorized splitting the shared skill into per-forge variants. The PR defers this, instead conditionalizing the shared skill with forge-conditional guards and forge-neutral language. Combined with the forge-specific skills loaded via harness, the functional risk is substantially mitigated.

  • [scope-deviation] scripts/lib/post-failure-report.lib.sh — Issue Make code agent multi-forge (GitHub + GitLab) #807 design said leave post-failure-report.lib.sh untouched for fix agent compatibility. The PR modifies it with declare -F dispatch guards, GitLab token sanitization, and GITLAB_TOKEN redaction. The changes are backward-compatible — when forge functions are not defined (as in the fix agent), the code falls through to the existing gh CLI paths.

  • [scope-deviation] scripts/lib/pr-assignee.lib.sh — Issue Make code agent multi-forge (GitHub + GitLab) #807 design stated leave pr-assignee.lib.sh untouched for fix agent compatibility. The PR modifies it with the same declare -F dispatch pattern. Changes are backward-compatible.

  • [scope-creep] scripts/post-fix.shpost-fix.sh was not in issue Make code agent multi-forge (GitHub + GitLab) #807's scope but receives token sanitization changes (glpat- redaction, oauth2: redaction, PRIVATE-TOKEN header, GITLAB_TOKEN literal redaction) and forge-dispatch fallbacks via its bundled post-failure-report.lib.sh copy. Natural consequence of shared library modifications and constitutes defensive hardening.

  • [env-file-quoting-inconsistency] env/github/code.env:2 — GH_TOKEN is exported without quotes while ISSUE_URL and FULLSEND_FORGE are quoted. Pre-existing pattern in env/code.env, not a regression.

Previous run

Review

Findings

Medium

  • [protected-path] .github/scripts/select-eval-agents-test.sh, agents/code.md, harness/code.yaml, policies/github/code.yaml, policies/gitlab/code.yaml, scripts/lib/code-ops.lib.sh, scripts/lib/github-code-ops.lib.sh, scripts/lib/gitlab-code-ops.lib.sh, scripts/lib/post-failure-report.lib.sh, scripts/lib/pr-assignee.lib.sh, scripts/post-code-test.sh, scripts/post-code.sh, scripts/post-code.src.sh, scripts/post-fix.sh, scripts/pre-code-test.sh, scripts/pre-code.sh, scripts/pre-code.src.sh, skills/code-implementation/SKILL.md — 18 of 26 changed files are under protected paths (agents/, harness/, policies/, scripts/, skills/, .github/). The PR links to issue Make code agent multi-forge (GitHub + GitLab) #807 and provides detailed rationale for the multi-forge architecture following the triage agent pattern from PR feat: make triage agent multi-forge (GitHub + GitLab) #686. Human approval is required for protected-path changes regardless of context.

  • [error-handling] scripts/lib/pr-assignee.lib.sh:170 — The GitHub forge_get_pr_details implementation includes || true, causing it to always return exit code 0. This makes the || { _pr_assignee_warn ...; return 0 } guard in maybe_assign_pr unreachable for GitHub. On gh pr view failure, pr_json is empty, the jq fallback produces existing_count='0', and the code proceeds to attempt assignment on a PR whose assignee state is unknown. Pre-refactor, this failure triggered a warning and skipped assignment. The GitLab implementation does not have this issue (curl --fail propagates errors correctly).

Low

  • [logic-error] scripts/lib/gitlab-code-ops.lib.sh:93 — forge_get_repo_merge_methods never sets squash (s) to true. GitLab controls squash via squash_option, not merge_method. forge_enable_auto_merge ignores the _method_flag parameter. However, enable_auto_merge in post-code.src.sh short-circuits for GitLab before reaching the merge-method resolution code, so CODE_AUTO_MERGE_METHOD has no effect on GitLab — the project-level merge_method setting determines the merge behavior.

  • [gitlab-token-scope-breadth] harness/code.yaml — The GitLab sandbox receives GITLAB_TOKEN with the full api scope, granting read and write access. Unlike GitHub where GH_TOKEN is explicitly scoped to read-only, GitLab has no equivalent scope restriction. Compensating controls: network policy (access: read-only) and binary restriction to curl.

  • [gitlab-host-injection-into-sandbox] env/gitlab/code.env:3 — GITLAB_HOST is injected into the sandbox. Pre/post scripts on the runner validate it against ISSUE_URL and an allowlist. Network policy endpoint restrictions are the enforcement mechanism inside the sandbox.

  • [gha-log-masking-gap] scripts/post-code.src.sh::add-mask:: is applied to PUSH_TOKEN but not separately to GITLAB_TOKEN. In practice, GITLAB_TOKEN is set to PUSH_TOKEN at push time, so the value is covered by the existing mask. Defense-in-depth gap for future scenarios.

  • [edge-case] scripts/lib/gitlab-code-ops.lib.sh:62 — forge_validate_issue_url has a hardcoded allowlist of GitLab hosts (gitlab.com and gitlab.cee.redhat.com). Self-hosted instances not in this allowlist will fail validation. Both the validation function and the network policy must be updated together. Documented in docs/code.md.

  • [logic-error] scripts/lib/gitlab-code-ops.lib.sh:202 — forge_write_output is a no-op when GITHUB_OUTPUT is unset (normal on GitLab CI). The pr_url output is silently dropped. Documented as intentional.

  • [consumer-completeness] scripts/lib/gitlab-code-ops.lib.sh:142 — forge_list_prs_for_issue returns empty on API failure (|| true) rather than failing closed. Matches the identical GitHub implementation pattern.

  • [dead-code] scripts/post-fix.sh:270 — post-fix.sh does not source code-ops.lib.sh, so declare -F forge_* dispatch checks in its bundled post-failure-report.lib.sh copy are dead code. The fallback to gh CLI is correct.

  • [sandbox-network-policy-broadening] policies/gitlab/code.yaml:49 — GitLab sandbox policy allows **/curl binary for gitlab_api network access. Architecturally necessary (no glab CLI equivalent). Restricted to gitlab.com and gitlab.cee.redhat.com endpoints with access: read-only.

  • [assignee-validation-relaxation] scripts/lib/pr-assignee.lib.sh:191 — Assignee format validation regex relaxed from ^[a-zA-Z0-9_-]+$ to ^[a-zA-Z0-9_.-]+$, adding dot for GitLab username compatibility. The remaining regex is still restrictive.

  • [scope-gap] skills/code-implementation/SKILL.md — Issue Make code agent multi-forge (GitHub + GitLab) #807 Step 5 authorized splitting the shared skill into per-forge variants. The PR defers this, instead conditionalizing the shared skill with forge-conditional guards and forge-neutral language. Combined with the forge-specific skills loaded via harness, the functional risk is substantially mitigated.

  • [scope-deviation] scripts/lib/post-failure-report.lib.sh — Issue Make code agent multi-forge (GitHub + GitLab) #807 design said leave post-failure-report.lib.sh untouched for fix agent compatibility. The PR modifies it with declare -F dispatch guards, GitLab token sanitization, and GITLAB_TOKEN redaction. The changes are backward-compatible — when forge functions are not defined (as in the fix agent), the code falls through to the existing gh CLI paths.

  • [scope-creep] scripts/post-fix.shpost-fix.sh was not in issue Make code agent multi-forge (GitHub + GitLab) #807's scope but receives token sanitization changes (glpat- redaction, oauth2: redaction, PRIVATE-TOKEN header, GITLAB_TOKEN literal redaction) and forge-dispatch fallbacks via its bundled post-failure-report.lib.sh copy. Natural consequence of shared library modifications and constitutes defensive hardening.

Previous run (2)

Review

Findings

Medium

  • [protected-path] .github/scripts/select-eval-agents-test.sh, agents/code.md, harness/code.yaml, policies/github/code.yaml, policies/gitlab/code.yaml, scripts/lib/code-ops.lib.sh, scripts/lib/github-code-ops.lib.sh, scripts/lib/gitlab-code-ops.lib.sh, scripts/lib/post-failure-report.lib.sh, scripts/lib/pr-assignee.lib.sh, scripts/post-code-test.sh, scripts/post-code.sh, scripts/post-code.src.sh, scripts/post-fix.sh, scripts/pre-code-test.sh, scripts/pre-code.sh, scripts/pre-code.src.sh, skills/code-implementation/SKILL.md — 18 of 24 changed files are under protected paths (agents/, harness/, policies/, scripts/, skills/, .github/). The PR links to issue Make code agent multi-forge (GitHub + GitLab) #807 and provides detailed rationale for the multi-forge architecture following the triage agent pattern from PR feat: make triage agent multi-forge (GitHub + GitLab) #686. Human approval is required for protected-path changes regardless of context.

Low

  • [logic-error] scripts/lib/gitlab-code-ops.lib.sh:302 — forge_get_repo_merge_methods never sets squash (s) to true. GitLab controls squash via squash_option, not merge_method. forge_enable_auto_merge ignores the _method_flag parameter. However, enable_auto_merge in post-code.src.sh short-circuits for GitLab before reaching the merge-method resolution code, so CODE_AUTO_MERGE_METHOD has no effect on GitLab — the project-level merge_method setting determines the merge behavior.

  • [gitlab-token-scope-breadth] harness/code.yaml:102 — The GitLab sandbox receives GITLAB_TOKEN with the full api scope, granting read and write access. Unlike GitHub where GH_TOKEN is explicitly scoped to read-only, GitLab has no equivalent scope restriction. Compensating controls: network policy (access: read-only) and binary restriction to curl.

  • [gitlab-host-injection-into-sandbox] env/gitlab/code.env:3 — GITLAB_HOST is injected into the sandbox. Pre/post scripts on the runner validate it against ISSUE_URL and an allowlist. Network policy endpoint restrictions are the enforcement mechanism inside the sandbox.

  • [stale-terminology] README.md:37 — Architecture section says "runs on the GitHub Actions runner." With multi-forge support, this should say "CI runner."

  • [stale-terminology] README.md:39 — Architecture section says "GitHub mutations." With GitLab support, this should say "forge mutations."

  • [edge-case] scripts/lib/gitlab-code-ops.lib.sh:73 — forge_validate_issue_url has a hardcoded allowlist of GitLab hosts (gitlab.com and gitlab.cee.redhat.com). Self-hosted instances not in this allowlist will fail validation. Both the validation function and the network policy must be updated together. Documented in docs/code.md.

  • [logic-error] scripts/lib/gitlab-code-ops.lib.sh:412 — forge_write_output is a no-op when GITHUB_OUTPUT is unset (normal on GitLab CI). The pr_url output is silently dropped. Documented as intentional.

  • [edge-case] scripts/lib/gitlab-code-ops.lib.sh:319 — GitLab forge_enable_auto_merge has a narrow TOCTOU window between checking pipeline status and issuing the merge API call. Consequence is benign (the pipeline passed). Same race exists in the GitHub path.

  • [consumer-completeness] scripts/lib/gitlab-code-ops.lib.sh:152 — forge_list_prs_for_issue returns empty on API failure (|| true) rather than failing closed. Matches the identical GitHub implementation pattern.

  • [dead-code] scripts/post-fix.shpost-fix.sh does not source code-ops.lib.sh, so declare -F forge_* dispatch checks in its bundled post-failure-report.lib.sh copy are dead code. The fallback to gh CLI is correct.

  • [sandbox-network-policy-broadening] policies/gitlab/code.yaml:57 — GitLab sandbox policy allows **/curl binary for gitlab_api network access. Architecturally necessary (no glab CLI equivalent). Restricted to gitlab.com and gitlab.cee.redhat.com endpoints with access: read-only.

  • [gha-log-masking-gap] scripts/post-code.src.sh:307 — ::add-mask:: is applied to PUSH_TOKEN but not separately to GITLAB_TOKEN. In practice, GITLAB_TOKEN is set to PUSH_TOKEN at push time, so the value is covered by the existing mask. Defense-in-depth gap for future scenarios.

  • [scope-gap] skills/code-implementation/SKILL.md — Issue Make code agent multi-forge (GitHub + GitLab) #807 Step 5 authorized splitting the shared skill into per-forge variants. The PR defers this, instead conditionalizing the shared skill with forge-conditional guards and forge-neutral language. Combined with the forge-specific skills loaded via harness, the functional risk is substantially mitigated.

  • [scope-deviation] scripts/lib/post-failure-report.lib.sh — Issue Make code agent multi-forge (GitHub + GitLab) #807 design said leave post-failure-report.lib.sh untouched for fix agent compatibility. The PR modifies it with declare -F dispatch guards, GitLab token sanitization, and GITLAB_TOKEN redaction. The changes are backward-compatible — when forge functions are not defined (as in the fix agent), the code falls through to the existing gh CLI paths.

  • [scope-creep] scripts/post-fix.shpost-fix.sh was not in issue Make code agent multi-forge (GitHub + GitLab) #807's scope but receives token sanitization changes (glpat- redaction, oauth2: redaction, PRIVATE-TOKEN header, GITLAB_TOKEN literal redaction) and forge-dispatch fallbacks via its bundled post-failure-report.lib.sh copy. Natural consequence of shared library modifications and constitutes defensive hardening.

  • [guard-pattern-consistency] scripts/pre-code.src.sh — The triage pre/post scripts validate FULLSEND_FORGE early with ${FULLSEND_FORGE:?...}. The code pre/post scripts omit this guard, relying on code-ops.lib.sh's case statement to fail. Different error message but functionally equivalent behavior.

  • [quoting-consistency] harness/code.yaml — The forge.*.env.runner blocks quote passthrough variables ("${PUSH_TOKEN}"). The triage harness leaves runner passthrough vars unquoted. Within the code harness the quoting is internally consistent; the divergence is cross-file.

  • [stale-path] FEATURES.md:165 — Step 11 instructs contributors to "Update policies/<agent>.yaml." For multi-forged agents, the policy path is now policies/<forge>/<agent>.yaml.

Previous run (3)

Review

Findings

Medium

  • [protected-path] .github/scripts/select-eval-agents-test.sh, agents/code.md, harness/code.yaml, policies/github/code.yaml, policies/gitlab/code.yaml, scripts/lib/code-ops.lib.sh, scripts/lib/github-code-ops.lib.sh, scripts/lib/gitlab-code-ops.lib.sh, scripts/lib/post-failure-report.lib.sh, scripts/lib/pr-assignee.lib.sh, scripts/post-code-test.sh, scripts/post-code.sh, scripts/post-code.src.sh, scripts/post-fix.sh, scripts/pre-code-test.sh, scripts/pre-code.sh, scripts/pre-code.src.sh, skills/code-implementation/SKILL.md — 18 of 24 changed files are under protected paths (agents/, harness/, policies/, scripts/, skills/, .github/). The PR links to issue Make code agent multi-forge (GitHub + GitLab) #807 and provides detailed rationale for the multi-forge architecture following the triage agent pattern from PR feat: make triage agent multi-forge (GitHub + GitLab) #686. Human approval is required for protected-path changes regardless of context.

  • [stale-reference] policies/code.yaml — The old top-level policies/code.yaml file is no longer referenced by the harness (the policy: key was removed from harness/code.yaml and replaced with per-forge policies at policies/github/code.yaml and policies/gitlab/code.yaml), but the old file was not deleted. It remains on disk as dead configuration that could confuse users who expect it to be authoritative. Consider deleting it or adding a comment stating it is superseded.

Low

  • [edge-case] scripts/lib/gitlab-code-ops.lib.shforge_validate_issue_url has a hardcoded allowlist of GitLab hosts: gitlab.com and gitlab.cee.redhat.com. Self-hosted instances not in this allowlist will fail validation. Both the validation function and the network policy (policies/gitlab/code.yaml) must be updated together. The docs/code.md "Multi-forge support" section now documents this requirement.

  • [logic-error] scripts/lib/gitlab-code-ops.lib.shforge_get_repo_merge_methods maps GitLab merge_method to a GitHub-compatible JSON shape but never sets s (squash) to true. GitLab controls squash via squash_option (a separate project setting), not merge_method. Additionally, forge_enable_auto_merge ignores the _method_flag parameter entirely, so CODE_AUTO_MERGE_METHOD=squash is silently discarded on GitLab.

  • [logic-error] scripts/lib/gitlab-code-ops.lib.shforge_write_output is a no-op when GITHUB_OUTPUT is unset, which is the normal case on GitLab CI. The pr_url output written by post-code.src.sh is silently dropped on GitLab. The function explicitly documents this as intentional.

  • [logic-error] scripts/lib/gitlab-code-ops.lib.sh — GitLab forge_enable_auto_merge has a narrow TOCTOU window: between checking pipeline status and issuing the PUT /merge_requests/:iid/merge call, the pipeline could complete, triggering an immediate merge. The consequence is benign (the pipeline passed) and the same race exists conceptually in the GitHub path.

  • [consumer-completeness] scripts/lib/gitlab-code-ops.lib.shforge_list_prs_for_issue returns empty on API failure (|| true) rather than failing closed. Matches the identical GitHub implementation pattern — correct for best-effort search where empty is safer than aborting.

  • [api-contract] scripts/lib/gitlab-code-ops.lib.shforge_add_label passes labels via --data-urlencode "add_labels=${label}". The GitLab API interprets add_labels as a comma-separated string, so labels containing commas would be split. Current callers only pass controlled label names (ready-for-review, pr-open) that do not contain commas.

  • [dead-code] scripts/post-fix.shpost-fix.sh does not source code-ops.lib.sh, so declare -F forge_* dispatch checks in its bundled post-failure-report.lib.sh copy are dead code. The fallback to gh CLI is correct, so the dead code is harmless.

  • [scope-gap] skills/code-implementation/SKILL.md — Issue Make code agent multi-forge (GitHub + GitLab) #807 Step 5 authorized splitting the shared skill into per-forge variants. The PR defers this, instead conditionalizing the shared skill with forge-conditional guards and forge-neutral language. Combined with the forge-specific skills loaded via harness, the functional risk is substantially mitigated.

  • [scope-deviation] scripts/lib/post-failure-report.lib.sh — Issue Make code agent multi-forge (GitHub + GitLab) #807 design said leave post-failure-report.lib.sh untouched for fix agent compatibility. The PR modifies it with declare -F dispatch guards, GitLab token sanitization, and GITLAB_TOKEN redaction. The changes are backward-compatible — when forge functions are not defined (as in the fix agent), the code falls through to the existing gh CLI paths.

  • [scope-creep] scripts/post-fix.shpost-fix.sh was not in issue Make code agent multi-forge (GitHub + GitLab) #807's scope but receives token sanitization changes (glpat- redaction, oauth2: redaction, PRIVATE-TOKEN header, GITLAB_TOKEN literal redaction) and forge-dispatch fallbacks via its bundled post-failure-report.lib.sh copy. These are a natural consequence of shared library modifications and constitute defensive hardening.

  • [sandbox-network-policy-broadening] policies/gitlab/code.yaml — GitLab sandbox policy allows **/curl binary for gitlab_api network access. Architecturally necessary (no glab CLI equivalent). Restricted to gitlab.com and gitlab.cee.redhat.com endpoints with access: read-only.

  • [gitlab-token-scope-breadth] scripts/lib/gitlab-code-ops.lib.sh — GitLab token requires broad api scope vs GitHub's granular scoping. This is a GitLab platform limitation — granular scopes are not available. Sandbox network policy (access: read-only) is the compensating control.

  • [quoting-consistency] harness/code.yaml — The forge.*.env.runner blocks quote passthrough variables ("${PUSH_TOKEN}"). The triage harness leaves runner passthrough vars unquoted. Within the code harness the quoting is internally consistent; the divergence is cross-file.

  • [guard-pattern-consistency] scripts/pre-code.src.sh — The triage pre/post scripts validate FULLSEND_FORGE early with ${FULLSEND_FORGE:?...}. The code pre/post scripts omit this guard, relying on code-ops.lib.sh's case statement to fail. Different error message but functionally equivalent behavior.

  • [minor-inaccuracy] docs/code.md — Line 113 says "Build and push the image to a container registry accessible from your GitHub Actions runners." With the code agent now supporting GitLab CI, this phrasing is GitHub-specific. Change to "CI runners."

Previous run (4)

Review

Findings

Medium

  • [protected-path] agents/code.md, harness/code.yaml, policies/github/code.yaml, policies/gitlab/code.yaml, scripts/lib/code-ops.lib.sh, scripts/lib/github-code-ops.lib.sh, scripts/lib/gitlab-code-ops.lib.sh, scripts/lib/post-failure-report.lib.sh, scripts/lib/pr-assignee.lib.sh, scripts/post-code-test.sh, scripts/post-code.sh, scripts/post-code.src.sh, scripts/post-fix.sh, scripts/pre-code-test.sh, scripts/pre-code.sh, scripts/pre-code.src.sh, skills/code-implementation/SKILL.md, .github/scripts/select-eval-agents-test.sh — 18 of 24 changed files are under protected paths (agents/, harness/, policies/, scripts/, skills/, .github/). The PR links to issue Make code agent multi-forge (GitHub + GitLab) #807 and provides detailed rationale for the multi-forge architecture following the triage agent pattern from PR feat: make triage agent multi-forge (GitHub + GitLab) #686. Human approval is required for protected-path changes regardless of context.

  • [edge-case] scripts/lib/gitlab-code-ops.lib.shforge_validate_issue_url has a hardcoded allowlist of GitLab hosts: gitlab.com and gitlab.cee.redhat.com. Self-hosted GitLab instances not in this allowlist will fail validation even if the network policy is extended via harness composition. Both the validation function and the network policy (policies/gitlab/code.yaml) must be updated together to support additional hosts. Consider making the host allowlist configurable via an environment variable or documenting this limitation prominently.

Low

  • [logic-error] scripts/lib/gitlab-code-ops.lib.shforge_get_repo_merge_methods maps GitLab merge_method to a GitHub-compatible JSON shape but never sets s (squash) to true. GitLab controls squash per-MR via a squash=true parameter on the merge API call, not as a project-level merge method. In practice, enable_auto_merge returns early for GitLab before reaching merge-method resolution, so this mapping is currently unreachable.

  • [api-contract] scripts/lib/gitlab-code-ops.lib.shforge_add_label passes labels via --data-urlencode "add_labels=${label}". The GitLab API interprets add_labels as a comma-separated string, so labels containing commas would be split. Current callers only pass controlled label names (ready-for-review, pr-open) that do not contain commas.

  • [quoting-consistency] harness/code.yaml — The forge.*.env.runner blocks quote passthrough variables ("${PUSH_TOKEN}"). If the triage harness uses a different convention (unquoted in env.runner), the inconsistency is cross-file. Internally consistent within this file.

  • [logic-error] scripts/lib/gitlab-code-ops.lib.shforge_write_output is a no-op when GITHUB_OUTPUT is unset, which is the normal case on GitLab CI. The pr_url output written by post-code.src.sh is silently dropped on GitLab. The function explicitly documents this as intentional.

  • [logic-error] scripts/lib/gitlab-code-ops.lib.sh — GitLab forge_enable_auto_merge has a narrow TOCTOU window: between checking pipeline status and issuing the PUT /merge_requests/:iid/merge call, the pipeline could complete, triggering an immediate merge. The consequence is benign (the pipeline passed) and the same race exists conceptually in the GitHub path.

  • [consumer-completeness] scripts/lib/gitlab-code-ops.lib.shforge_list_prs_for_issue returns empty on API failure (|| true) rather than failing closed. Matches the identical GitHub implementation pattern — correct for best-effort search where empty is safer than aborting.

  • [comment-reference-inconsistency] scripts/post-fix.shpost-fix.sh does not source code-ops.lib.sh, so declare -F forge_* dispatch checks in its bundled post-failure-report.lib.sh copy are dead code. The fallback to gh CLI is correct, so the dead code is harmless.

  • [sandbox-network-policy-broadening] policies/gitlab/code.yaml — GitLab sandbox policy allows **/curl binary for gitlab_api network access. Architecturally necessary (no glab CLI equivalent). Restricted to gitlab.com and gitlab.cee.redhat.com endpoints with access: read-only.

  • [gitlab-token-scope-breadth] scripts/lib/gitlab-code-ops.lib.sh — GitLab token requires broad api scope vs GitHub's granular scoping. This is a GitLab platform limitation — granular scopes are not available. Sandbox network policy (access: read-only) is the compensating control.

  • [scope-gap] skills/code-implementation/SKILL.md — Issue Make code agent multi-forge (GitHub + GitLab) #807 Step 5 authorized splitting the shared skill into per-forge variants. The PR defers this, instead conditionalizing the shared skill with forge-conditional guards and forge-neutral language. Combined with the forge-specific skills loaded via harness, the functional risk is substantially mitigated.

  • [scope-creep] scripts/post-fix.shpost-fix.sh was not in issue Make code agent multi-forge (GitHub + GitLab) #807's scope but receives token sanitization changes (glpat- redaction, oauth2: redaction, PRIVATE-TOKEN header, GITLAB_TOKEN literal redaction) and forge-dispatch fallbacks via its bundled post-failure-report.lib.sh copy. These are a natural consequence of shared library modifications and constitute defensive hardening.

  • [naming-consistency] scripts/lib/gitlab-code-ops.lib.sh — Private helper functions _gitlab_code_api and _gitlab_code_api_with_status use a code infix specific to this agent. The file is explicitly scoped to the code agent by name, so the naming is currently consistent.

  • [guard-pattern-consistency] scripts/pre-code.src.sh — The triage pre/post scripts validate FULLSEND_FORGE early with ${FULLSEND_FORGE:?...}. The code pre/post scripts omit this guard, relying on code-ops.lib.sh's case statement to fail. Different error message but functionally equivalent behavior.

  • [minor-inaccuracy] skills/code-implementation/SKILL.md — Line 59 says "This uses GitHub Actions annotation syntax so it surfaces in the run summary." On GitLab CI, ::notice:: annotations are not recognized and appear as plain text. The annotation is harmless but the claim is GitHub-specific.

  • [minor-inaccuracy] skills/code-implementation/SKILL.md — Line 453 says "the GitHub Actions runner". The PR updates the same phrasing in post-code.sh and post-code.src.sh to "CI runner" but the SKILL.md reference was not updated to match.

Previous run (5)

Review

Findings

Medium

  • [protected-path] agents/code.md, harness/code.yaml, policies/github/code.yaml, policies/gitlab/code.yaml, scripts/lib/code-ops.lib.sh, scripts/lib/github-code-ops.lib.sh, scripts/lib/gitlab-code-ops.lib.sh, scripts/lib/post-failure-report.lib.sh, scripts/lib/pr-assignee.lib.sh, scripts/post-code-test.sh, scripts/post-code.sh, scripts/post-code.src.sh, scripts/post-fix.sh, scripts/pre-code-test.sh, scripts/pre-code.sh, scripts/pre-code.src.sh, skills/code-implementation/SKILL.md, .github/scripts/select-eval-agents-test.sh — 18 of 24 changed files are under protected paths (agents/, harness/, policies/, scripts/, skills/, .github/). The PR links to issue Make code agent multi-forge (GitHub + GitLab) #807 and provides detailed rationale for the multi-forge architecture following the triage agent pattern from PR feat: make triage agent multi-forge (GitHub + GitLab) #686. Human approval is required for protected-path changes regardless of context.

Low

  • [scope-gap] skills/code-implementation/SKILL.md — Issue Make code agent multi-forge (GitHub + GitLab) #807 Step 5 authorized splitting the shared skill into per-forge variants. The PR defers this, instead conditionalizing the shared skill with forge-conditional guards and forge-neutral language. Compared to the prior review, hardcoded gh commands in steps 2, 4, and 8 have been replaced with forge-neutral guidance directing the agent to its forge-specific skill. Remaining gh CLI references in steps 1 and 3 are inside forge-conditional guards. Combined with the forge-specific skills loaded via harness, the functional risk is substantially mitigated.

  • [error-handling-idiom] scripts/lib/gitlab-code-ops.lib.sh — GitLab forge_assign_pr uses gha_echo warning directly instead of _pr_assignee_warn (which is used by the GitHub forge_assign_pr). Both ultimately call gha_echo, but the inconsistency means the GitLab path misses _pr_assignee_warn's stderr fallback. Replace gha_echo warning calls with _pr_assignee_warn to match the GitHub implementation.

  • [logic-error] scripts/lib/gitlab-code-ops.lib.shforge_write_output is a no-op when GITHUB_OUTPUT is unset, which is the normal case on GitLab CI. The pr_url output written by post-code.src.sh is silently dropped on GitLab. The function explicitly documents this as intentional.

  • [logic-error] scripts/lib/gitlab-code-ops.lib.sh — GitLab forge_enable_auto_merge has a narrow TOCTOU window: between checking pipeline status and issuing the PUT /merge_requests/:iid/merge call, the pipeline could complete, triggering an immediate merge. The consequence is benign (the pipeline passed) and the same race exists conceptually in the GitHub path.

  • [consumer-completeness] scripts/lib/gitlab-code-ops.lib.shforge_list_prs_for_issue returns empty on API failure (|| true) rather than failing closed. Matches the identical GitHub implementation pattern — correct for best-effort search where empty is safer than aborting.

  • [sandbox-network-policy-broadening] policies/gitlab/code.yaml — GitLab sandbox policy allows **/curl binary for gitlab_api network access. Architecturally necessary (no glab CLI equivalent). Restricted to gitlab.com and gitlab.cee.redhat.com endpoints with access: read-only.

  • [gitlab-token-scope-breadth] scripts/lib/gitlab-code-ops.lib.sh — GitLab token requires broad api scope vs GitHub's granular scoping. This is a GitLab platform limitation — granular scopes are not available. Sandbox network policy (access: read-only) is the compensating control.

  • [naming-consistency] scripts/lib/gitlab-code-ops.lib.sh — Private helper functions _gitlab_code_api and _gitlab_code_api_with_status use a code infix specific to this agent. The file is explicitly scoped to the code agent by name, so the naming is currently consistent.

  • [scope-creep] scripts/post-fix.shpost-fix.sh was not in issue Make code agent multi-forge (GitHub + GitLab) #807's scope but receives token sanitization changes (glpat- redaction, oauth2: redaction, PRIVATE-TOKEN header, GITLAB_TOKEN literal redaction) and forge-dispatch fallbacks via its bundled post-failure-report.lib.sh copy. These are a natural consequence of shared library modifications and constitute defensive hardening.

  • [comment-reference-inconsistency] scripts/post-fix.shpost-fix.sh does not source code-ops.lib.sh, so declare -F forge_* dispatch checks in its bundled post-failure-report.lib.sh copy are dead code. The fallback to gh CLI is correct, so the dead code is harmless.

  • [minor-inaccuracy] docs/network-policy.md — The sentence "The defaults cover Vertex AI, the GitHub API, package registries, and gitleaks releases" now only describes the GitHub-forge defaults. With multi-forge support, GitLab policies cover the GitLab API instead.

  • [missing-documentation] docs/code.md — No "Multi-forge support" / migration notes section analogous to docs/triage.md. Users extending harness/code.yaml via base: composition may need to know that the policy field moved from top-level to forge.<platform>.policy, ISSUE_URL replaces GITHUB_ISSUE_URL in scripts, and FULLSEND_FORGE is now required.

Previous run (6)

Review

Findings

Medium

  • [protected-path] agents/code.md, harness/code.yaml, policies/github/code.yaml, policies/gitlab/code.yaml, scripts/lib/code-ops.lib.sh, scripts/lib/github-code-ops.lib.sh, scripts/lib/gitlab-code-ops.lib.sh, scripts/lib/post-failure-report.lib.sh, scripts/lib/pr-assignee.lib.sh, scripts/post-code-test.sh, scripts/post-code.sh, scripts/post-code.src.sh, scripts/post-fix.sh, scripts/pre-code-test.sh, scripts/pre-code.sh, scripts/pre-code.src.sh, skills/code-implementation/SKILL.md, .github/scripts/select-eval-agents-test.sh — 18 of 24 changed files are under protected paths (agents/, harness/, policies/, scripts/, skills/, .github/). The PR links to issue Make code agent multi-forge (GitHub + GitLab) #807 and provides detailed rationale for the multi-forge architecture following the triage agent pattern from PR feat: make triage agent multi-forge (GitHub + GitLab) #686. Human approval is required for protected-path changes regardless of context.

  • [design-coherence] skills/code-implementation/SKILL.md — The shared code-implementation skill hardcodes gh CLI commands (gh issue view, gh pr view, gh pr list, gh pr diff, gh repo view) throughout steps 1, 2, and 3. On GitLab forge deployments (FULLSEND_FORGE=gitlab), the gh binary is excluded from the sandbox network policy — only curl is permitted. The skill's "Commands you will need" section and step 1 "Fetch the issue" will guide the agent to use a blocked binary. Issue Make code agent multi-forge (GitHub + GitLab) #807 planned per-forge skill variants but the PR deferred this. The forge-specific skills (skills/github-forge, skills/gitlab-forge) provide some alternative guidance, but contradictory instructions between the shared and forge-specific skills may confuse the agent.

Low

  • [logic-error] scripts/lib/gitlab-code-ops.lib.shforge_write_output is a no-op when GITHUB_OUTPUT is unset, which is the normal case on GitLab CI. The pr_url output written by post-code.src.sh is silently dropped on GitLab. The function explicitly documents this as intentional ("GitLab CI uses artifacts or dotenv for output; write to GITHUB_OUTPUT if available, otherwise no-op"). The MR URL is printed to stdout, so the harness can capture it.

  • [logic-error] scripts/lib/gitlab-code-ops.lib.sh — GitLab forge_enable_auto_merge has a narrow TOCTOU window: between checking pipeline status and issuing the PUT /merge_requests/:iid/merge call, the pipeline could complete, triggering an immediate merge. The consequence is benign (the pipeline passed) and the same race exists conceptually in the GitHub path.

  • [consumer-completeness] scripts/lib/gitlab-code-ops.lib.shforge_list_prs_for_issue returns empty on API failure (|| true) rather than failing closed. Matches the identical GitHub implementation pattern — correct for best-effort search where empty is safer than aborting.

  • [sandbox-network-policy-broadening] policies/gitlab/code.yaml — GitLab sandbox policy allows **/curl binary for gitlab_api network access. Architecturally necessary (no glab CLI equivalent). Restricted to gitlab.com and gitlab.cee.redhat.com endpoints with access: read-only.

  • [gitlab-token-scope-breadth] scripts/lib/gitlab-code-ops.lib.sh — GitLab token requires broad api scope vs GitHub's granular scoping. This is a GitLab platform limitation — granular scopes are not available. Sandbox network policy (access: read-only) is the compensating control.

  • [missing-documentation] LOCAL.md — GitLab setup instructions could be clearer that only one of GITHUB_ISSUE_URL or GITLAB_ISSUE_URL should be set, and FULLSEND_FORGE should match the chosen forge.

Previous run (7)

Review

Findings

Medium

  • [protected-path] agents/code.md, harness/code.yaml, policies/github/code.yaml, policies/gitlab/code.yaml, scripts/lib/code-ops.lib.sh, scripts/lib/github-code-ops.lib.sh, scripts/lib/gitlab-code-ops.lib.sh, scripts/lib/post-failure-report.lib.sh, scripts/lib/pr-assignee.lib.sh, scripts/post-code-test.sh, scripts/post-code.sh, scripts/post-code.src.sh, scripts/post-fix.sh, scripts/pre-code-test.sh, scripts/pre-code.sh, scripts/pre-code.src.sh, skills/code-implementation/SKILL.md — 17 of 23 changed files are under protected paths (agents/, harness/, policies/, scripts/, skills/). The PR links to issue Make code agent multi-forge (GitHub + GitLab) #807 and provides detailed rationale for the multi-forge architecture following the triage agent pattern from PR feat: make triage agent multi-forge (GitHub + GitLab) #686. Human approval is required for protected-path changes regardless of context.

Low

  • [consumer-completeness] scripts/lib/gitlab-code-ops.lib.sh:250 — GitLab forge_write_output is a no-op when GITHUB_OUTPUT is unset (the normal case on GitLab CI). The pr_url output written by post-code.src.sh is dropped on GitLab. The function documents this as a known limitation. No data is silently lost — the MR URL is printed to stdout.

  • [logic-error] scripts/lib/gitlab-code-ops.lib.sh:197 — GitLab forge_enable_auto_merge has a narrow TOCTOU window: between checking pipeline status and issuing the PUT /merge_requests/:iid/merge call, the pipeline could complete, triggering an immediate merge. The consequence is benign (the pipeline passed) and the same race exists conceptually in the GitHub path.

  • [stale-reference] .github/scripts/select-eval-agents-test.sh:99 — Test fixture creates a synthetic harness with policy: policies/code.yaml, the old undifferentiated policy path. The PR removes the top-level policy: from harness/code.yaml in favor of forge-specific forge.<platform>.policy paths.

  • [assignee-validation-regex-relaxed] scripts/lib/pr-assignee.lib.sh:185 — Assignee validation regex relaxed from ^[a-zA-Z0-9_-]+$ to ^[a-zA-Z0-9_.-]+$, adding dot for GitLab username compatibility. The expansion is safe (dot matches only literal dot in a character class) but the original defense-in-depth comment was removed.

  • [sandbox-network-policy-broadening] policies/gitlab/code.yaml:38 — GitLab sandbox policy allows **/curl binary for gitlab_api network access. Architecturally necessary (no glab CLI equivalent). Restricted to gitlab.com and gitlab.cee.redhat.com endpoints with access: read-only.

  • [gitlab-token-scope-breadth] scripts/lib/gitlab-code-ops.lib.sh:19 — GitLab token requires broad api scope vs GitHub's granular scoping. Sandbox network policy (access: read-only) is the compensating control.

  • [design-documentation-mismatch] scripts/lib/gitlab-code-ops.lib.sh:996forge_list_prs_for_issue returns empty on API failure (|| true) rather than failing closed. Matches the identical GitHub implementation pattern — correct for best-effort search where empty is safer than aborting.

  • [stale-reference] agents/review.md:29 — References GITHUB_ISSUE_URL. Currently correct for the GitHub-only review agent; may need updating when multi-forge is extended.

  • [stale-reference] agents/prioritize.md:16 — References GITHUB_ISSUE_URL. Same as above for the prioritize agent.

  • [naming-conventions] env/github/code.env:3, env/gitlab/code.env:4FULLSEND_FORGE exported without quotes while other exports in the same files use quoted values.

Previous run (8)

Review

Findings

Medium

  • [stale-reference] harness/code.yaml:17 — Top-level policy: policies/code.yaml still references the old undifferentiated policy file. The PR creates forge-specific policies under forge.github.policy and forge.gitlab.policy, but the top-level policy: field was not updated or removed. If the harness falls back to the top-level policy when no forge-specific config matches, the agent would use the old generic policy (which still exists on disk since this PR does not delete it). The triage agent's harness (established pattern from PR feat: make triage agent multi-forge (GitHub + GitLab) #686) has no top-level policy: — only forge-level ones.
    Remediation: Either remove the top-level policy: field to match the triage harness pattern, or update it to a sensible default. Consider whether policies/code.yaml should be deleted.

  • [protected-path] agents/code.md, harness/code.yaml, policies/github/code.yaml, policies/gitlab/code.yaml, scripts/lib/code-ops.lib.sh, scripts/lib/github-code-ops.lib.sh, scripts/lib/gitlab-code-ops.lib.sh, scripts/lib/post-failure-report.lib.sh, scripts/lib/pr-assignee.lib.sh, scripts/post-code-test.sh, scripts/post-code.sh, scripts/post-code.src.sh, scripts/post-fix.sh, scripts/pre-code-test.sh, scripts/pre-code.sh, scripts/pre-code.src.sh — 16 of 22 changed files are under protected paths (agents/, harness/, policies/, scripts/). The PR links to issue Make code agent multi-forge (GitHub + GitLab) #807 and provides detailed rationale for the multi-forge architecture following the triage agent pattern from PR feat: make triage agent multi-forge (GitHub + GitLab) #686. Human approval is required for protected-path changes regardless of context.

Low

  • [logic-error] scripts/lib/gitlab-code-ops.lib.sh:151 — GitLab forge_enable_auto_merge checks pipeline status before calling PUT /merge_requests/:iid/merge with merge_when_pipeline_succeeds=true. A narrow TOCTOU window exists: if the pipeline completes between the status check and the PUT call, the MR merges immediately. The consequence is benign (the pipeline did pass) and the same race exists conceptually in the GitHub path.

  • [sandbox-network-policy-broadening] policies/gitlab/code.yaml:38 — New GitLab sandbox policy allows **/curl binary for gitlab_api network access. Architecturally necessary since there is no glab CLI equivalent, but represents wider attack surface than the GitHub path where curl is excluded. The curl binary is restricted to the gitlab_api network policy endpoints (gitlab.com and gitlab.cee.redhat.com) only.

  • [stale-reference] skills/code-implementation/SKILL.md:4 — The skill frontmatter says "GitHub issue" but the code agent now supports both GitHub and GitLab. The PR updated agents/code.md to remove GitHub-specific phrasing but did not update this skill file. See also: line 301 with GitHub-specific network policy wording.

  • [stale-reference] docs/code.md:91 — Required binaries row lists git, gh, scan-secrets, pre-commit. On GitLab, gh is not used (curl is used instead). The row is now GitHub-specific.

  • [behavioral-divergence] scripts/lib/gitlab-code-ops.lib.sh:93 — GitLab forge_get_default_branch accepts a token parameter but ignores it (uses global GITLAB_TOKEN via _gitlab_code_api), while the GitHub implementation honors the passed token. Works in practice since default branch info only requires read access.

  • [test-coverage-gap] scripts/post-code-test.sh:1987 — New GitLab tests validate URL parsing, token sanitization, and dispatch patterns in isolation. No integration test runs the actual post-code script with FULLSEND_FORGE=gitlab end-to-end.

Previous run (9)

Review

Findings

High

  • [error-handling-gap] scripts/lib/post-failure-report.lib.shreport_post_failure_to_issue() uses gh issue comment directly instead of the forge-neutral forge_post_issue_comment(). On the GitLab forge path, gh cannot talk to GitLab, so all failure reporting (secret-scan failures, pre-commit blocks, push rejections, PR creation errors) will silently fail — no diagnostic comment reaches the GitLab issue. Additionally, _post_failure_ensure_token() only sets GH_TOKEN (not GITLAB_TOKEN), and build_post_failure_comment() calls the GitHub-only post_failure_workflow_run_url() instead of forge_get_workflow_run_url(). While the PR deliberately left this library untouched for fix agent compatibility, the consequence is that the GitLab path has no failure observability. See also: [token-redaction-gap] finding at the same file.
    Remediation: Update report_post_failure_to_issue(), _post_failure_ensure_token(), and build_post_failure_comment() to dispatch through forge-neutral functions when FULLSEND_FORGE is set, while preserving the existing interface for the fix agent.

Medium

  • [error-handling-regression] scripts/post-code.src.sh — The EXISTING_PR_NUM assignment (step 8, "Create PR/MR") drops the || true that the original code had. Under set -euo pipefail, if the forge API call fails (transient error, rate limit), the script aborts after the branch is already pushed in step 7 — leaving a pushed branch with no PR/MR created. The failure is recoverable on re-run, but the regression from the original error-tolerant behavior should be fixed.
    Remediation: Add || true to match original behavior: EXISTING_PR_NUM="$(forge_list_prs_for_branch "${BRANCH}")" || true.

  • [token-redaction-gap] scripts/lib/post-failure-report.lib.shsanitize_failure_detail() redacts GH_TOKEN and GitHub PAT patterns (ghp_*, github_pat_*) but not GITLAB_TOKEN or GitLab PAT format (glpat-*). The literal PUSH_TOKEN redaction partially covers this (since GITLAB_TOKEN="${PUSH_TOKEN}"), but independently-appearing GitLab PATs in error output would not be caught.
    Remediation: Add GITLAB_TOKEN literal redaction and glpat-[A-Za-z0-9_-]{20,} regex pattern.

  • [protected-path] agents/code.md, harness/code.yaml, policies/github/code.yaml, policies/gitlab/code.yaml, scripts/lib/code-ops.lib.sh, scripts/lib/github-code-ops.lib.sh, scripts/lib/gitlab-code-ops.lib.sh, scripts/post-code-test.sh, scripts/post-code.sh, scripts/post-code.src.sh, scripts/pre-code-test.sh, scripts/pre-code.sh, scripts/pre-code.src.sh — 13 of 16 changed files are under protected paths (agents/, harness/, policies/, scripts/). The PR links to issue Make code agent multi-forge (GitHub + GitLab) #807 and provides detailed rationale for the multi-forge architecture. Human approval is required for protected-path changes regardless of context.

  • [stale-reference] LOCAL.md:41,59 — References GITHUB_ISSUE_URL in local testing instructions and prose. The env architecture now uses ISSUE_URL (set from GITHUB_ISSUE_URL in env/github/code.env). GITHUB_ISSUE_URL is still the source variable for local setup, but the instructions should also mention FULLSEND_FORGE=github and ISSUE_URL.
    Remediation: Update local testing instructions to include FULLSEND_FORGE and explain the variable relationship.

  • [stale-path] docs/code.md:83, docs/network-policy.md:43,100 — Reference policies/code.yaml. The file still exists as the top-level harness base policy, but the active per-forge policies are now at policies/github/code.yaml and policies/gitlab/code.yaml. Documentation is incomplete rather than broken.
    Remediation: Update references to mention per-forge policy files alongside the base policy.

  • [incomplete-documentation] docs/code.md:49CODE_AUTO_MERGE variable description says "enable GitHub auto-merge on PRs" but the implementation now supports both GitHub (gh pr merge --auto) and GitLab (merge_when_pipeline_succeeds).
    Remediation: Make description forge-agnostic and note platform-specific behavior.

  • [env-var-interface] env/code.envGITHUB_ISSUE_URL removed from shared env. The variable is still available via forge-specific paths (env/github/code.env maps it to ISSUE_URL), and the agent prompt uses ISSUE_URL, not GITHUB_ISSUE_URL. Direct consumers of the shared env file will not find it.
    Remediation: Document the migration path for any direct consumers of env/code.env.

  • [script-interface] scripts/lib/code-ops.lib.sh — Scripts now require FULLSEND_FORGE env var (fail-closed exit 1 on invalid values). The harness always provides this for both forge types, but direct script invocation (local development, external CI) without FULLSEND_FORGE will fail with a clear error message.
    Remediation: Document the new requirement in the script headers and docs/code.md.

Low

  • [test-coverage-gap] scripts/post-code-test.sh — No test cases exercise the GitLab forge path. All tests set FULLSEND_FORGE=github. Issue Make code agent multi-forge (GitHub + GitLab) #807 called for GitLab-specific tests following the triage pattern.

  • [missing-documentation] docs/code.mdFULLSEND_FORGE not listed in the Variables table despite being a new required env var for the code agent.

  • [pagination-gap] scripts/lib/gitlab-code-ops.lib.sh:151forge_list_prs_for_issue() fetches only the first page (100 MRs) without pagination. Best-effort optimization path with low practical impact.

  • [gitlab-host-default] scripts/post-code.src.shGITLAB_HOST defaults to gitlab.com when unset. Correct for SaaS GitLab users, but self-hosted instances (e.g., gitlab.cee.redhat.com) must explicitly set the variable.


Labels: PR modifies the code agent (agents/, harness/, scripts/, policies/)


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.

@ggallen
ggallen force-pushed the agent/807-multi-forge-code branch from b6f6f6a to 55b5185 Compare August 15, 2026 01:28
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 15, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:30 AM UTC · Completed 1:49 AM UTC

Commit: 55b5185 · View workflow run →

@fullsend-ai-review
fullsend-ai-review Bot dismissed their stale review August 15, 2026 01:49

Superseded by updated review

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Aug 15, 2026
@ggallen
ggallen force-pushed the agent/807-multi-forge-code branch from 55b5185 to 569e30f Compare August 15, 2026 02:07
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 15, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 2:08 AM UTC · Ended 2:15 AM UTC

Commit: 569e30f · View workflow run →

@ggallen
ggallen force-pushed the agent/807-multi-forge-code branch from 569e30f to 3171e2f Compare August 15, 2026 02:15
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 15, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:16 AM UTC · Completed 2:36 AM UTC

Commit: 3171e2f · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@ggallen
ggallen force-pushed the agent/807-multi-forge-code branch from 3171e2f to 61fca6f Compare August 15, 2026 02:40
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 15, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:42 AM UTC · Completed 3:04 AM UTC

Commit: 61fca6f · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@ggallen
ggallen force-pushed the agent/807-multi-forge-code branch from 61fca6f to 65cb980 Compare August 15, 2026 03:08
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 15, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 3:09 AM UTC · Completed 3:28 AM UTC

Commit: 65cb980 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@ggallen
ggallen force-pushed the agent/807-multi-forge-code branch from 65cb980 to dd3424a Compare August 15, 2026 03:30
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 15, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 3:31 AM UTC · Ended 3:35 AM UTC

Commit: dd3424a · View workflow run →

@ggallen
ggallen force-pushed the agent/807-multi-forge-code branch from dd3424a to 37d2e76 Compare August 15, 2026 03:34
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 15, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 3:36 AM UTC · Completed 3:51 AM UTC

Commit: 37d2e76 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@ggallen
ggallen force-pushed the agent/807-multi-forge-code branch from 37d2e76 to 8381f0a Compare August 15, 2026 03:54
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 15, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 3:55 AM UTC · Completed 4:14 AM UTC

Commit: 8381f0a · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@ggallen
ggallen force-pushed the agent/807-multi-forge-code branch from 8381f0a to a3a5f9d Compare August 15, 2026 04:17
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 15, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:19 AM UTC · Completed 4:40 AM UTC

Commit: a3a5f9d · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@ggallen
ggallen force-pushed the agent/807-multi-forge-code branch from a3a5f9d to 15c968f Compare August 15, 2026 04:45
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 15, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:47 AM UTC · Completed 5:04 AM UTC

Commit: 15c968f · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

Add forge-dispatch architecture to the code agent, following
the patterns established by the triage agent in PR #686.

New ops libraries:
- scripts/lib/code-ops.lib.sh: forge dispatcher (FULLSEND_FORGE)
- scripts/lib/github-code-ops.lib.sh: GitHub impl using gh CLI
- scripts/lib/gitlab-code-ops.lib.sh: GitLab impl using curl

Refactored scripts:
- pre-code.src.sh: uses forge_* functions for URL validation,
  existing-PR check, label/comment operations
- post-code.src.sh: uses forge_* functions for push auth,
  PR/MR creation, auto-merge, assignee resolution, labels,
  comments, and CI URL generation

Configuration:
- harness/code.yaml: added forge.gitlab section with policy,
  skills, host_files, and env vars
- policies/github/code.yaml: existing policy moved to forge dir
- policies/gitlab/code.yaml: GitLab-specific network policy
  (curl binary, gitlab.com endpoints)
- env/github/code.env: GitHub-specific vars (ISSUE_URL, GH_TOKEN)
- env/gitlab/code.env: GitLab-specific vars (ISSUE_URL,
  GITLAB_TOKEN)
- env/code.env: shared vars only (git identity, timeouts, Go)

Key design decisions:
- PR_NUMBER_FROM_URL convention preserved to avoid SC2153
- forge_list_prs_for_branch propagates errors (fail-closed)
- post-failure-report.lib.sh left untouched (fix agent compat)
- pr-assignee.lib.sh left untouched; GitLab ops remap response
  shapes to match GitHub expected format

Related to #807

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Greg Allen <gallen@redhat.com>
@ggallen
ggallen force-pushed the agent/807-multi-forge-code branch from 15c968f to d7e7cbb Compare August 15, 2026 05:05
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 15, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:06 AM UTC · Completed 5:26 AM UTC

Commit: d7e7cbb · View workflow run →

@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.

GITLAB_HOST=$(echo "${url}" | sed -E 's|^https://([^/]+)/.*|\1|')
REPO_FULL_NAME=$(echo "${url}" | sed -E 's|^https://[^/]+/(.+)/-/issues/[0-9]+$|\1|')
REPO_ENCODED=$(printf '%s' "${REPO_FULL_NAME}" | jq -sRr @uri)
ISSUE_NUMBER=$(basename "${url}")

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] logic-error

forge_get_repo_merge_methods never sets squash (s) to true. GitLab controls squash via squash_option, not merge_method. forge_enable_auto_merge ignores the _method_flag parameter. However, enable_auto_merge in post-code.src.sh short-circuits for GitLab before reaching the merge-method resolution code, so CODE_AUTO_MERGE_METHOD has no effect on GitLab.

if [[ -z "${project_id}" ]]; then
gha_echo warning "Could not resolve project ID for fork-MR filtering — failing closed"
return 1
fi

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] logic-error

forge_write_output is a no-op when GITHUB_OUTPUT is unset (normal on GitLab CI). The pr_url output is silently dropped. Documented as intentional.

_gitlab_code_api POST "/projects/${REPO_ENCODED}/issues/${ISSUE_NUMBER}/notes" \
--data-urlencode "body=${body}" > /dev/null 2>/dev/null
}

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] consumer-completeness

forge_list_prs_for_issue returns empty on API failure (|| true) rather than failing closed. Matches the identical GitHub implementation pattern.

Comment thread scripts/post-fix.sh
@@ -265,6 +270,10 @@ EOF

post_failure_workflow_run_url() {

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] dead-code

post-fix.sh does not source code-ops.lib.sh, so declare -F forge_* dispatch checks in its bundled post-failure-report.lib.sh copy are dead code. The fallback to gh CLI is correct.

Comment thread policies/gitlab/code.yaml
port: 443
protocol: rest
enforcement: enforce
access: read-only

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] sandbox-network-policy-broadening

GitLab sandbox policy allows **/curl binary for gitlab_api network access. Architecturally necessary (no glab CLI equivalent). Restricted to gitlab.com and gitlab.cee.redhat.com endpoints with access: read-only.

return 0
fi

echo "Assigning PR #${target_pr} to ${assignee}..."

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] assignee-regex-relaxation

Assignee format validation regex relaxed from ^[a-zA-Z0-9_-]+$ to ^[a-zA-Z0-9_.-]+$, adding dot for GitLab username compatibility. No injection risk from adding dots to the allowed character set.

Comment thread env/github/code.env
@@ -0,0 +1,3 @@
export ISSUE_URL="${GITHUB_ISSUE_URL}"
export GH_TOKEN=${GH_TOKEN}

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] env-file-quoting-inconsistency

GH_TOKEN is exported without quotes while ISSUE_URL and FULLSEND_FORGE are quoted. Pre-existing pattern in env/code.env, not a regression.

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.

Make code agent multi-forge (GitHub + GitLab)

1 participant