Skip to content

feat(code-agent): use PR template for pr_body in structured output#56

Open
rh-hemartin wants to merge 1 commit into
mainfrom
feat/pr-body
Open

feat(code-agent): use PR template for pr_body in structured output#56
rh-hemartin wants to merge 1 commit into
mainfrom
feat/pr-body

Conversation

@rh-hemartin

@rh-hemartin rh-hemartin commented Jul 8, 2026

Copy link
Copy Markdown
Member

Summary

  • Migrate PR template / pr_body support from feat(code-agent): uses PR template if found fullsend#2979 to the agents repo
  • Agent discovers repo PR templates and writes a template-structured pr_body field in code-result.json
  • Post-script uses pr_body verbatim as PR description, falling back to commit body when absent
  • Includes all review feedback fixes: printf over echo, tightened sed patterns, maxLength constraint, jq -n examples, if/else clarity

Test plan

  • post-code-test.sh passes all 62 tests including new pr_body cases
  • Verify schema validation accepts pr_body field in CI
  • End-to-end: code agent run on a repo with a PR template produces template-structured PR description

Migrated from fullsend-ai/fullsend#2979. Closes fullsend-ai/fullsend#1575

🤖 Generated with Claude Code

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Use repo PR templates to populate structured pr_body for PR creation

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Add optional pr_body field to code-agent result schema for template-structured PR descriptions.
• Update post-code script to prefer pr_body verbatim, falling back to commit body.
• Extend docs/tests to cover PR template discovery and pr_body behavior (Closes stripping).
Diagram

graph TD
  T["PR templates"] --> A["Code agent"] --> R["code-result.json"] --> P["post-code.sh"] --> G{{"GitHub PR"}}
  S[("code-result.schema.json")] --> R
  D["SKILL.md guidance"] --> A

  subgraph Legend
    direction LR
    _file["File"] ~~~ _proc["Process"] ~~~ _ext{{"External"}} ~~~ _schema[("Schema")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Have post-code.sh discover templates and assemble PR body
  • ➕ Keeps behavior centralized in the post-script (single source of truth)
  • ➕ Doesn’t require agent-side template parsing heuristics
  • ➖ Makes post-code.sh significantly more complex (template selection, comment stripping, section prompting)
  • ➖ Harder to tailor content to actual changes without agent context
2. Introduce structured PR-body fields (e.g., summary/testing/notes) instead of free-form markdown
  • ➕ Enables stronger validation and consistent formatting
  • ➕ Easier to enforce required sections across repos
  • ➖ Requires ongoing schema evolution and mapping to arbitrary repo templates
  • ➖ More work for agents and post-script to render to template-specific markdown
3. Continue using commit-body unwrapping only (no pr_body)
  • ➕ No new schema surface area
  • ➕ Simpler pipeline end-to-end
  • ➖ Cannot reliably match repo PR templates
  • ➖ Still constrained by gitlint body line-length rules and unwrapping heuristics

Recommendation: Current approach (agent writes template-structured pr_body, post-script uses it verbatim) is the best trade-off: the agent has the richest context to populate template prompts, while the post-script remains a thin consumer with a clear fallback. The main thing to watch is the line-stripping logic (Signed-off-by/Closes) to avoid accidentally deleting legitimate content; the tightened sed patterns and added tests address this risk.

Files changed (4) +164 / -31

Enhancement (1) +22 / -9
post-code.shPrefer pr_body from result file when creating PR +22/-9

Prefer pr_body from result file when creating PR

• Reads '.pr_body' from the agent result JSON (via 'jq') and, when present, uses it as the PR description verbatim after stripping Signed-off-by/Closes lines. Retains the legacy fallback path that unwraps the commit body via awk when 'pr_body' is absent.

scripts/post-code.sh

Tests (1) +86 / -1
post-code-test.shExtend post-code tests for agent-provided pr_body path +86/-1

Extend post-code tests for agent-provided pr_body path

• Updates 'build_pr_body' to accept an optional agent-provided 'pr_body' and strip Signed-off-by/Closes lines before appending canonical metadata once. Adds new test helpers and cases to ensure 'pr_body' is used verbatim (no wrapping) and that only a single 'Closes #<n>' is present.

scripts/post-code-test.sh

Documentation (1) +50 / -20
SKILL.mdDocument PR template discovery and safe jq -n pr_body writing +50/-20

Document PR template discovery and safe jq -n pr_body writing

• Adds explicit guidance to check for PR templates and structure the agent-produced 'pr_body' to match visible template sections (skipping HTML comments). Replaces heredoc examples with 'jq -n --arg' patterns to avoid shell expansion issues and clarifies that 'pr_body' is not subject to gitlint body line-length limits.

skills/code-implementation/SKILL.md

Other (1) +6 / -1
code-result.schema.jsonAdd optional pr_body to code-result schema +6/-1

Add optional pr_body to code-result schema

• Extends the schema description and adds an optional 'pr_body' string field with 'maxLength: 65536'. This enables the agent to provide a full PR description independently of the commit body and gitlint wrapping constraints.

schemas/code-result.schema.json

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 8, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 7:24 AM UTC · Completed 7:38 AM UTC
Commit: f828ec7 · View workflow run →

@rh-hemartin rh-hemartin self-assigned this Jul 8, 2026
@qodo-code-review

qodo-code-review Bot commented Jul 8, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Context used
✅ Compliance rules (platform): 55 rules
✅ Skills: 4 invoked
  code-review
  code-implementation
  pr-review
  docs-review

Grey Divider


Action required

1. Protected paths modified (scripts/, skills/) 📜 Skill insight § Compliance
Description
This PR modifies protected governance/infrastructure paths (scripts/ and skills/), which require
explicit human review and must not be auto-approved. Ensure the PR has explicit
justification/authorization and is routed for human approval.
Code

scripts/post-code.sh[R446-460]

echo "Creating PR..."

COMMIT_SUBJECT="$(git log -1 --format='%s' HEAD)"
-COMMIT_BODY_RAW="$(git log -1 --format='%b' HEAD | sed '/^Signed-off-by:/d' | sed '/^Closes #/d' | sed -e :a -e '/^\n*$/{ $d; N; ba; }')"
-
-COMMIT_BODY="$(echo "${COMMIT_BODY_RAW}" | awk '
-  /^$/           { if (buf) print buf; print; buf=""; next }
-  /^[-*#>]|^  /  { if (buf) print buf; buf=""; print; next }
-  /^Closes /     { if (buf) print buf; buf=""; print; next }
-                 { buf = (buf ? buf " " $0 : $0) }
-  END            { if (buf) print buf }
-')"
+
+# Read pr_body from agent output. Fall back to commit body if absent.
+PR_BODY_FROM_RESULT=""
+if [ -n "${RESULT_FILE}" ]; then
+  PR_BODY_FROM_RESULT="$(jq -r '.pr_body // empty' "${RESULT_FILE}" 2>/dev/null || true)"
+fi
+
+if [ -n "${PR_BODY_FROM_RESULT}" ]; then
+  # Agent provided pr_body (template-aware or best-effort).
+  # Strip Signed-off-by and Closes lines so the script appends them once.
+  COMMIT_BODY="$(printf '%s\n' "${PR_BODY_FROM_RESULT}" | sed '/^Signed-off-by:/d' | sed '/^Closes #/d; /^Closes [a-zA-Z0-9_.-]*\/[a-zA-Z0-9_.-]*#/d' | sed -e :a -e '/^\n*$/{ $d; N; ba; }')"
+else
Relevance

⭐⭐⭐ High

Repo added CODEOWNERS/ruleset enforcement and treats governance paths as requiring explicit human
review/justification.

PR-#27
PR-#29

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The compliance rule requires a finding whenever protected paths are modified. The diff shows changes
under scripts/ and skills/, triggering the protected-path requirement.

scripts/post-code.sh[446-470]
scripts/post-code-test.sh[127-157]
skills/code-implementation/SKILL.md[167-176]
Skill: pr-review

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

## Issue description
Protected paths were modified in this PR; per policy this must not be auto-approved and needs explicit justification/authorization plus human review.

## Issue Context
The repository treats changes under `scripts/` and `skills/` as protected governance/infrastructure modifications.

## Fix Focus Areas
- scripts/post-code.sh[446-470]
- scripts/post-code-test.sh[127-157]
- skills/code-implementation/SKILL.md[167-176]

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


2. jq failure ignored for pr_body ✓ Resolved 📜 Skill insight ☼ Reliability
Description
scripts/post-code.sh suppresses JSON parse/read failures when extracting .pr_body by using
2>/dev/null || true, causing a silent fallthrough to the commit-body path. This violates the
requirement that inter-component/guard failure paths be handled explicitly rather than ignored.
Code

scripts/post-code.sh[453]

+  PR_BODY_FROM_RESULT="$(jq -r '.pr_body // empty' "${RESULT_FILE}" 2>/dev/null || true)"
Relevance

⭐⭐ Medium

No prior accepted/rejected guidance found on handling jq parse failures vs silent fallback in
scripts.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The rule forbids silently ignoring failure paths for guards/contracts. The added .pr_body consumer
swallows jq errors and proceeds without warning, making contract failures non-obvious at runtime.

scripts/post-code.sh[450-454]
Skill: pr-review

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

## Issue description
`jq` failures while reading `.pr_body` from the agent result are silenced (`2>/dev/null || true`), which can hide a broken producer/consumer contract and silently change runtime behavior.

## Issue Context
This is an inter-component contract: the agent produces `code-result.json`, and `post-code.sh` consumes it to build the PR body.

## Fix Focus Areas
- scripts/post-code.sh[450-454]

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



Remediation recommended

3. Docs contradict pr_body support ✓ Resolved 📜 Skill insight ⚙ Maintainability
Description
Repository documentation about code-result.json is stale and internally contradictory: it still
describes structured output as containing only target_branch (and even “exactly one field”)
despite the schema and scripts now supporting an optional pr_body. This mismatch creates
misleading guidance for agents and reviewers about whether pr_body is permitted and how validation
behaves.
Code

schemas/code-result.schema.json[R15-19]

+    "pr_body": {
+      "type": "string",
+      "maxLength": 65536,
+      "description": "PR description used as PR body instead of commit body. Supports markdown formatting and template-compliant structure without gitlint line-length constraints."
    }
Relevance

⭐⭐⭐ High

Team recently tightened structured-output docs/validation; likely will fix contradictory docs around
code-result.json fields.

PR-#24

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The schemas/code-result.schema.json has been extended to include an optional pr_body, and the
consumer behavior (including post-code.sh) reads .pr_body when present, but the documentation
has not been updated consistently: skills/code-implementation/SKILL.md step 3 instructs agents to
emit {target_branch, pr_body} while the later “Validate structured output” step 11 still claims
the JSON must contain exactly one field and that only target_branch is allowed (extra fields fail
validation). Additionally, agents/code.md discusses structured output only in terms of
target-branch selection, reinforcing outdated guidance; together these citations show the docs
contradict the current schema/behavior.

schemas/code-result.schema.json[15-19]
skills/code-implementation/SKILL.md[810-820]
agents/code.md[95-101]
skills/code-implementation/SKILL.md[199-226]
schemas/code-result.schema.json[7-19]
Skill: docs-review
Skill: pr-review

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

## Issue description
Documentation describing structured output for `code-result.json` is now stale and contradictory because the schema (and consumer behavior) supports an optional `pr_body`, yet parts of the docs still claim the output must contain exactly one field and/or only `target_branch` is allowed.

## Issue Context
- The harness schema (`schemas/code-result.schema.json`) has been extended to allow optional `pr_body` alongside `target_branch`.
- `skills/code-implementation/SKILL.md` is internally inconsistent: step 3 instructs writing `{target_branch, pr_body}`, but the later “Validate structured output” step 11 still says the JSON must contain exactly one field and that any extra fields fail validation.
- `agents/code.md` still describes structured output primarily/only in terms of target-branch selection, which is now incomplete.
- Goal: align docs with the current schema and validation rules (including that `additionalProperties: false` still applies, but both keys are allowed and `pr_body` is optional).

## Fix Focus Areas
- schemas/code-result.schema.json[15-19]
- skills/code-implementation/SKILL.md[810-820]
- agents/code.md[95-101]

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


4. Over-broad pr_body stripping ✓ Resolved 🐞 Bug ☼ Reliability
Description
When pr_body is present, scripts/post-code.sh deletes any line that starts at column 0 with
Signed-off-by: or Closes ... before using it as the PR description. This can unintentionally
remove legitimate visible content (e.g., a template prompt/example or a code block line) that
happens to begin with those exact prefixes.
Code

scripts/post-code.sh[R450-460]

+# Read pr_body from agent output. Fall back to commit body if absent.
+PR_BODY_FROM_RESULT=""
+if [ -n "${RESULT_FILE}" ]; then
+  PR_BODY_FROM_RESULT="$(jq -r '.pr_body // empty' "${RESULT_FILE}" 2>/dev/null || true)"
+fi
+
+if [ -n "${PR_BODY_FROM_RESULT}" ]; then
+  # Agent provided pr_body (template-aware or best-effort).
+  # Strip Signed-off-by and Closes lines so the script appends them once.
+  COMMIT_BODY="$(printf '%s\n' "${PR_BODY_FROM_RESULT}" | sed '/^Signed-off-by:/d' | sed '/^Closes #/d; /^Closes [a-zA-Z0-9_.-]*\/[a-zA-Z0-9_.-]*#/d' | sed -e :a -e '/^\n*$/{ $d; N; ba; }')"
+else
Relevance

⭐⭐ Medium

No historical evidence on whether they consider sed-based stripping too broad for pr_body content.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR body is piped through sed delete commands before being used; those commands match at
start-of-line and will delete any such line regardless of whether it’s intended as an auto-close
footer or legitimate template/content text.

scripts/post-code.sh[450-470]
scripts/post-code-test.sh[127-157]

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 `pr_body` path is meant to be used largely verbatim, but the current `sed '/^Signed-off-by:/d'` and `sed '/^Closes .../d'` deletes matching lines anywhere in the PR body. If a repo PR template contains a visible “Closes #…” prompt/example, or a markdown code block includes a line that begins with `Closes #`, that line will be silently removed.

### Issue Context
- `post-code.sh` appends its own `Closes #<ISSUE_NUMBER>` footer, so it’s reasonable to remove *footer* `Closes` lines from agent-provided text.
- The current implementation removes these lines regardless of position.

### Fix Focus Areas
- scripts/post-code.sh[450-470]
- scripts/post-code-test.sh[127-157]

### What to change
- Change sanitization to only remove `Signed-off-by:` / `Closes ...` lines when they are part of the trailing footer block (e.g., strip only contiguous matching lines at the end, after trimming trailing blank lines).
- Update `post-code-test.sh`’s `build_pr_body` helper to match the production logic.
- Add a test where `pr_body` contains a code block or example line starting with `Closes #...` *not at the end* and assert it remains present.

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


Grey Divider

Qodo Logo

Comment thread scripts/post-code.sh
Comment thread scripts/post-code.sh Outdated
Comment thread schemas/code-result.schema.json
Comment thread scripts/post-code.sh
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [protected-path] agents/code.md, scripts/post-code.sh, scripts/post-code-test.sh, skills/code-implementation/SKILL.md — This PR modifies files under protected paths (agents/, scripts/, skills/). The PR references feat(code-agent): uses PR template if found fullsend#2979 as the migration source and explains the rationale for each change. Human approval is always required for protected-path changes regardless of context.

Low

  • [test-coverage] scripts/post-code-test.sh — No test for the jq extraction path edge cases in post-code.sh (malformed JSON in RESULT_FILE, pr_body as null vs absent vs empty string). The production code handles these gracefully via 2>/dev/null and exit code checking with fallback to empty, but an explicit test would lock in the contract.

  • [test-coverage] scripts/post-code-test.sh — No test for the case where pr_body is provided AND commit_body is also non-empty. The code correctly prioritizes pr_body (checked first via the if-branch), but an explicit test would document and lock in the precedence behavior.

  • [prompt-injection-surface] skills/code-implementation/SKILL.md:169 — Step 3 item 5 instructs the code agent to read PR template files from target repos (.github/pull_request_template.md, etc.). A malicious template could contain adversarial instructions targeting the agent. Risk is bounded by defense-in-depth (sandbox isolation, post-script secret scanning and pre-commit hooks, review agent) and is consistent with the existing threat surface from reading CLAUDE.md and CONTRIBUTING.md from untrusted repos.

Prior review resolution

# Prior finding Status
1 [low] test-coverage: trailing blank lines before footer ✅ Resolved — pr-body-trailing-blanks-before-footer test added
2 [low] prompt-injection-surface: PR template reading ➡ Unchanged — risk bounded by defense-in-depth

Protected paths detected — this PR modifies files under one or more
protected paths. The review agent cannot approve PRs that touch these paths.
A human reviewer must approve this PR.

Protected files in this PR:

  • agents/code.md
  • scripts/post-code-test.sh
  • scripts/post-code.sh
  • skills/code-implementation/SKILL.md
Previous run

Review

Verdict: approve · 2 low findings

Clean feature addition. The prior medium-severity finding (step 11 in SKILL.md contradicting the new pr_body field by stating "exactly one field" and "only target_branch allowed") has been resolved — the updated diff now correctly says "target_branch (required) and optionally pr_body" and "Only target_branch and pr_body are allowed."

The architecture follows the established structured-output pattern cleanly: agent writes pr_body to code-result.json, post-script reads it via jq, strips trailing footer lines with a well-constructed awk pipeline, and falls back to commit body when absent. The schema change is additive-only (optional field, no required-array change), so existing outputs continue to validate. Shell handling is safe throughout — printf '%s\n' prevents backslash interpretation, double-quoting prevents word splitting, jq --arg avoids shell expansion when writing, and the data flows to gh pr create --body as a single quoted argument with no secondary shell interpretation.

The awk footer-stripping logic is more comprehensive than the legacy path: it handles Closes, Closed, Close, Fixes, Fixed, Fix, Resolves, Resolved, Resolve (case-flexible), and cross-repo references (org/repo#N), while only stripping from the end to preserve matching lines in body content. The legacy path continues unchanged for backward compatibility.

Test coverage is solid: 8 new test cases cover the happy path, verbatim pass-through (no unwrapping), strip-to-empty fallback, cross-repo Closes stripping, content-embedded Closes preservation, and Fixes/Resolves keyword variants.

Findings

1. [low] [test-coverage] scripts/post-code-test.sh

No test for pr_body with multiple consecutive trailing blank lines before a footer line (e.g., "Summary\n\n\n\nCloses #42"). The awk logic handles this correctly (blank lines are in the strip set and the while loop processes them), but an explicit test would lock in the behavior and catch regressions if the awk pattern changes.

2. [low] [prompt-injection-surface] skills/code-implementation/SKILL.md · step 3 item 5

The new step instructs the agent to read PR template files from target repos (.github/pull_request_template.md, etc.). A malicious template could contain adversarial instructions targeting the agent. Risk is bounded: the agent operates in a sandbox, the instruction to "skip HTML comments" partially mitigates, and the post-script applies defense-in-depth gates (secret scan, pre-commit, branch validation, review agent). This is consistent with the existing threat surface from reading CLAUDE.md and CONTRIBUTING.md from untrusted repos.

Prior review resolution

# Prior finding Status
1 [medium] Step 11 stale instructions ✅ Resolved — text updated to reflect both fields
2 [low] agents/code.md incomplete description ✅ Resolved — structured output section updated
3 [low] Test coverage gaps ✅ Mostly resolved — Fixes/Resolves/cross-repo tests added; one minor gap remains (finding 1 above)
4 [low] Test reimplements production logic ➡ Unchanged — established pattern in this file

Protected paths detected — this PR modifies files under one or more
protected paths. The review agent cannot approve PRs that touch these paths.
A human reviewer must approve this PR.

Protected files in this PR:

  • agents/code.md
  • scripts/post-code-test.sh
  • scripts/post-code.sh
  • skills/code-implementation/SKILL.md
Previous run (2)

Review

Verdict: comment · 1 medium, 3 low findings

Clean feature migration. The architecture (agent writes pr_body to code-result.json, post-script reads it verbatim, falls back to commit body) fits the existing structured-output pattern. Schema change is backward compatible (additive optional field), shell handling is safe (printf '%s\n' + sed pipeline), and the test additions follow the established test-helper reimplementation pattern in post-code-test.sh. The jq error-suppression idiom matches the existing AGENT_TARGET extraction on line 80.

Findings

1. [medium] [stale-instructions] skills/code-implementation/SKILL.md · lines 780–790

Step 11 text contradicts the new pr_body field. Step 11 currently states: "The file must be valid JSON with exactly one field" and "Only the target_branch field is allowed. Any extra fields will cause validation to fail." Since this PR adds pr_body as a valid optional field in the schema, those statements are now factually wrong. An agent following step 11 literally may remove pr_body from the output before running fullsend-check-output, silently defeating the feature.

The risk is mitigated by the fact that step 3 (newly updated) explicitly instructs the agent to write pr_body, and fullsend-check-output will pass with pr_body present. However, the contradictory text creates unnecessary ambiguity.

Remediation: Update step 11 to reflect the new schema — change "exactly one field" to "the required field (target_branch) and optionally pr_body", update the JSON example to show both fields, and revise the compliance note.

2. [low] [stale-documentation] agents/code.md · lines 97–100

The structured output section says the file "documents the target branch for PR creation" and the post-script "reads this file to determine which branch to target the PR against." Now that the file also carries pr_body, this description is incomplete. The schema and SKILL.md are the authoritative references, so impact is minimal.

3. [low] [test-coverage] scripts/post-code-test.sh

The new pr_body tests cover the happy path (content present, verbatim pass-through, Closes deduplication) but omit several edge cases: (a) pr_body that becomes empty after Signed-off-by / Closes stripping — should fall back to the automated description via the existing if [ -z "${COMMIT_BODY}" ] guard; (b) pr_body with cross-repo closes (Closes org/repo#N); (c) pr_body with trailing blank lines. These are handled by existing fallback logic, so the risk is low.

4. [low] [code-organization] scripts/post-code-test.sh · line 127

build_pr_body() in the test file reimplements the production inline logic from post-code.sh. The sed pipelines currently match exactly. This follows the established pattern throughout post-code-test.sh (8+ helpers reimplement production logic for isolation testing), so it is consistent — but the duplication remains a future maintenance consideration.

Notes

  • The Closes-stripping sed patterns only handle the Closes keyword (not Fixes, Resolves, Fix, etc.). This is a pre-existing limitation shared with the legacy commit-body path and is not introduced by this PR. The PR actually improves coverage by adding cross-repo Closes stripping in the new path.
  • The asymmetry between the legacy path (sed '/^Closes #/d') and the new path (which adds cross-repo pattern) is an intentional improvement, not a regression.
  • The schema change is fully backward compatible: pr_body is optional, not in the required array, and additionalProperties: false is maintained. Existing outputs with only target_branch continue to validate.
  • Shell safety is sound: printf '%s\n' prevents backslash interpretation, double-quoting prevents word splitting, and sed reads from stdin. No injection vector exists in the new data path.

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review-squad pass (5 agents: Claude, Gemini, Codex) on top of the existing qodo-code-review findings. Posted 6 inline comments for medium+ issues not already covered by qodo's 4 comments (protected-paths, jq-failure-silencing, docs-contradiction, over-broad-stripping).

One additional MEDIUM finding couldn't be attached inline because it's on an unchanged line outside the diff:

[MEDIUM] Step 6's "Label-gated" early exit has no fullsend-check-output call (skills/code-implementation/SKILL.md, the "Label-gated" bullet under step 6, "Identify the task type"). The prior step 3 had an explicit instruction that any early exit (steps 4, 6, or 7) must run fullsend-check-output before exiting; this PR's diff removes that block. Steps 4, 7, 9c, and 11 each have their own inline call, and the "Already-fixed" bullet routes through step 7's call — but "Label-gated" has no call of its own and previously relied on the now-removed step 3 guidance. Suggest adding an explicit fullsend-check-output "${FULLSEND_OUTPUT_DIR}/code-result.json" call to that bullet.

No shell/command-injection issues found — verified gh pr create --body handling is safe against $()/backtick payloads in pr_body, and pr_body never reaches GITHUB_OUTPUT.

Comment thread skills/code-implementation/SKILL.md Outdated
Comment thread skills/code-implementation/SKILL.md Outdated
Comment thread skills/code-implementation/SKILL.md Outdated
Comment thread scripts/post-code-test.sh
Comment thread scripts/post-code.sh Outdated
Comment thread scripts/post-code-test.sh
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 10, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:00 AM UTC · Completed 7:08 AM UTC
Commit: 0c74ec6 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jul 10, 2026
Teach the code agent to discover repo PR templates and write a
template-structured pr_body field in code-result.json. The post-script
uses pr_body verbatim as the PR description, falling back to the commit
body when absent.

Changes:
- Add optional pr_body field to code-result.schema.json (maxLength: 65536)
- Add pr_body reading logic to post-code.sh with printf-safe piping
- Tighten Closes-line stripping to only match GitHub ref forms
- Add PR template discovery step to SKILL.md (step 3 item 5)
- Replace heredoc examples with jq -n pattern (safe from shell expansion)
- Add pr_body test cases to post-code-test.sh

Migrated from fullsend-ai/fullsend#2979.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Hector Martinez <hemartin@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 10, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:17 AM UTC · Completed 7:33 AM UTC
Commit: ee18489 · 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.

use it when writing the commit subject in step 10.
5. **Check for PR template.** Look for `.github/pull_request_template.md`,
`.github/PULL_REQUEST_TEMPLATE.md`, or `docs/pull_request_template.md`.
If a directory `.github/PULL_REQUEST_TEMPLATE/` exists with multiple

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] prompt-injection-surface

Step 3 item 5 instructs the code agent to read PR template files from target repos. A malicious template could contain adversarial instructions. Risk is bounded by defense-in-depth and is consistent with the existing threat surface from reading CLAUDE.md and CONTRIBUTING.md from untrusted repos.

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed requires-manual-review Review requires human judgment labels Jul 10, 2026

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review-squad pass (3 agents: Claude, Claude, Gemini) on top of the existing qodo-code-review and fullsend-ai-review bot history. All prior findings were re-verified against the live file content before this pass (three from the prior ad hoc review-squad round turned out to be false positives — ${FULLSEND_OUTPUT_FILE} undefined, ${pr_template} undefined, and the docs-say-"exactly one field" claim — none reproduce against the current file). Posting the unique medium-and-above findings from this round below as inline comments.

Most notable: a CRITICAL, empirically-reproduced command-injection bug in the step 10a jq --arg merge example, which slipped past three prior review rounds — including this repo's own deployed fullsend-ai-review bot explicitly asserting twice that "jq --arg avoids shell expansion." I verified this live: substituting a backtick-quoted command into the documented pattern causes bash to execute it before jq ever sees the string.

Also new: a HIGH finding that pr_body never passes through gitleaks/secret scanning (it lives under $FULLSEND_OUTPUT_DIR, outside the git tree gitleaks detect --source . scans), and a HIGH finding that pr_body's maxLength (65536) exactly matches GitHub's hard body-length ceiling with no headroom for the footer post-code.sh always appends.

shell expansion of `$` and backticks in markdown content:

```bash
jq --arg pb "<pr_body content>" '. + {pr_body: $pb}' \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[CRITICAL] jq --arg merge command is vulnerable to shell command substitution, despite the doc's own safety claim

The surrounding text (line 786) claims this pattern "avoids shell expansion of $ and backticks in markdown content." I verified live that it does not: substituting real pr_body content containing a backtick-quoted command (e.g. `npm test` in a Testing section) into jq --arg pb "<pr_body content>" causes bash to execute the backtick expression before jq ever sees the string — the command's output silently ends up baked into pr_body instead of the literal text. jq --arg only prevents JSON-escaping issues; it does nothing for shell parsing of the double-quoted argument it's embedded in.

Suggestion: Populate a variable via a quoted heredoc first (which disables expansion), then pass that variable to jq:

PR_BODY_CONTENT=$(cat <<'EOF'
<pr_body content>
EOF
)
jq --arg pb "$PR_BODY_CONTENT" '. + {pr_body: $pb}' ...

Also correct the false "avoids shell expansion" claim in the prose.

Comment thread scripts/post-code.sh
')"

# Read pr_body from agent output. Fall back to commit body if absent.
PR_BODY_FROM_RESULT=""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[HIGH] pr_body is never secret-scanned before becoming a public PR description

gitleaks detect --source . (line 246) scans the git working tree, but code-result.json (where pr_body lives) is written under $FULLSEND_OUTPUT_DIR, a directory outside the git repo tree entirely (per the runner docs, repo checkout is /tmp/workspace/repo while output is /tmp/workspace/output). pr_body is free-text up to 65536 chars the agent writes directly there — it's never staged, never committed, and therefore never crosses the "Authoritative secret scan — final gate before any push" that every other piece of agent-written content passes through. A secret pasted into the description (from a log, config, or prompt-injected template) reaches a public PR body unguarded.

Suggestion: Run gitleaks (or equivalent) against the raw pr_body string here before using it, and hard-fail/fall back to the commit body if a secret is detected — mirroring the existing Signed-off-by hard-block pattern at lines 257-264.

},
"pr_body": {
"type": "string",
"maxLength": 65536,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[HIGH] maxLength exactly matches GitHub's hard body-length limit, leaving no headroom for the footer post-code.sh always appends

65536 is GitHub's exact PR/issue body character ceiling. post-code.sh (lines 515-526) unconditionally appends a ~150-250 char footer (---, Closes #N, verification checklist) after the agent's pr_body. A schema-valid pr_body near the ceiling pushes the assembled body over GitHub's limit, so gh pr create fails outright with no automated recovery path.

Suggestion: Lower maxLength to reserve headroom for the footer (e.g. ~65000 minus a safety margin), or validate/truncate the assembled body length in post-code.sh before calling gh pr create.

Comment thread scripts/post-code.sh
end = NR
while (end > 0) {
l = lines[end]
if (l == "" || l ~ /^Signed-off-by:/ || l ~ /^[Cc]lose[sd]? (#|[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+#)/ || l ~ /^[Ff]ix(e[sd])? (#|[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+#)/ || l ~ /^[Rr]esolve[sd]? (#|[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+#)/)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[MEDIUM] Footer-stripping regex is not end-anchored — can silently delete real trailing content, not just the closing reference

Each alternative (^[Cc]lose[sd]? (#|...), etc.) is anchored at line-start only. Reproduced: pr_body ending in "...Did the thing.\n\nCloses #42 but leaves a follow-up needed for the migration script." has the entire last line dropped — including "but leaves a follow-up needed for the migration script" — not just the Closes #42 reference. No existing test exercises a closing-keyword line with trailing prose.

Suggestion: Anchor with $ after the reference (e.g. /^[Cc]lose[sd]? #[0-9]+$/ and the cross-repo equivalent), or only strip the line if nothing but whitespace remains after removing the matched reference. Add a test case for "closing keyword + trailing prose on the same line."

Comment thread scripts/post-code.sh
if [ -n "${PR_BODY_FROM_RESULT}" ]; then
# Agent provided pr_body — strip trailing footer lines (Signed-off-by,
# GitHub auto-close keywords) so the script appends them once.
# Only strips from the end to preserve matching lines in body content.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[MEDIUM] pr_body stripping only removes trailing footer lines, unlike the hard, anywhere-in-body block enforced for the commit path

The legacy commit-body path strips Signed-off-by: lines unconditionally, anywhere in the body (sed '/^Signed-off-by:/d', line 480), and section 3b hard-blocks the entire push (exit 1) if a Signed-off-by: trailer appears anywhere in the commit body (lines 257-264). This new pr_body path only strips these patterns from the contiguous trailing block — a Signed-off-by: or Closes #N line anywhere else in pr_body survives untouched and is published verbatim, with no equivalent hard-block check applied to this new field.

Suggestion: Either strip these patterns globally from pr_body (matching legacy behavior) or add an explicit hard-fail check scanning the whole pr_body string for ^Signed-off-by:, at the same severity as the existing commit-body check.

templates, use the one matching the issue context (e.g., `bug_report.md`
for bug fixes, `feature_request.md` for enhancements), or fall back to
the first/default template. If found, read it and note sections/prompts
— skip HTML comments (`<!-- ... -->`), extract only the visible headings

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[MEDIUM] No guidance for a PR template that contains only HTML comments

The instruction to "skip HTML comments... extract only the visible headings and prompts" doesn't address the common case where the entire template is HTML comments with no visible headings at all (many repos use comment-only templates purely as instructions to humans). It's unclear whether the agent should treat this as "no template found" or attempt to synthesize headings from the comment text (which would contradict "skip HTML comments").

Suggestion: Add an explicit rule: if a template resolves to no visible sections after stripping comments, treat it the same as "no template found" and fall back to the best-effort description format.

Comment thread scripts/post-code-test.sh
"42" "agent/42-widget" \
"Fixes #42" "no"

run_pr_body_test "pr-body-resolves-keyword-stripped" \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[MEDIUM] Missing test coverage for common closing-keyword variants the stripping regex doesn't match

The stripping regex requires a literal space between the keyword and #/owner/repo# (e.g. ^[Cc]lose[sd]? (#|...)). It doesn't match, and there's no test for, variants like Closes: #42 (colon-separated), closes(#42), or other plausible phrasings. If an agent writes one of these as the trailing line despite being told not to include closing references, it survives stripping and the final PR body ends up with a duplicate closing reference alongside the script's own appended Closes #N.

Suggestion: Add test cases for Closes: #N, closes(#N), and similar variants; broaden the regex if these are expected in practice, or explicitly document that only the tested patterns are defended against.

Comment thread scripts/post-code-test.sh
end = NR
while (end > 0) {
l = lines[end]
if (l == "" || l ~ /^Signed-off-by:/ || l ~ /^[Cc]lose[sd]? (#|[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+#)/ || l ~ /^[Ff]ix(e[sd])? (#|[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+#)/ || l ~ /^[Rr]esolve[sd]? (#|[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+#)/)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[MEDIUM] Footer-stripping awk logic is hand-duplicated between production and test with no shared source

This build_pr_body() test helper re-implements the exact 15-line awk stripping block from scripts/post-code.sh:463-477 by hand rather than sourcing it. This mirrors a pre-existing pattern for rewrite_title/build_pr_body, but this PR compounds it with a second, more complex duplicated block — a future fix to the regex in one copy (e.g. the end-anchor bug flagged separately in this review) can silently diverge from the other while tests stay green, giving false confidence.

Suggestion: Extract the shared awk script into a small sourceable helper file/function that both post-code.sh and post-code-test.sh load, so tests actually exercise production code instead of a hand-copied duplicate.

the first/default template. If found, read it and note sections/prompts
— skip HTML comments (`<!-- ... -->`), extract only the visible headings
and prompts. You will structure the `pr_body` field in the result file
to match template sections (see the structured output block below) — the post-script uses

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[MEDIUM] Stale forward-reference — "the structured output block below" doesn't contain pr_body

This phrase points at the code block immediately following in step 3, which (by this PR's own design) only writes target_branchpr_body is deliberately deferred to step 10a so a timeout never leaks placeholder text as the PR description. The "see below" pointer is factually wrong about where pr_body is actually written, and relies on the reader separately noticing the non-adjacent step 10a instruction. Looks like leftover phrasing from before the write was split into two steps.

Suggestion: Change to "You will structure the pr_body field later, in step 10a, to match these template sections" so the forward reference is accurate.

@@ -294,7 +295,11 @@ Before planning, determine what kind of work this issue requires:
issue.
- **Label-gated** — if the issue has a label like `do-not-implement` or a gate

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[MEDIUM, premature-decision] Template-to-issue-type matching (step 3 item 5) precedes the formal task-type classification here in step 6

Step 3 item 5 (line 169) asks the agent to select a template "matching the issue context (e.g., bug_report.md for bug fixes, feature_request.md for enhancements)" during repo-convention discovery — before this step, which is the procedure's own designated place for classifying bug vs. feature vs. test-only vs. already-fixed vs. label-gated. Template selection assumes a classification the process hasn't formally reached yet, with no cross-reference between the two steps — an unverified simplification shipped as final guidance.

Suggestion: Either move template selection after this step, or note in step 3 that it uses a preliminary/informal read of the issue that should be reconciled with the formal classification here if they conflict (e.g., a "bug_report" template picked early for an issue this step later classifies as "already-fixed" or "test-only").

@ralphbean ralphbean left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. One minor note inline. I agree with @waynesun09's findings on the jq --arg safety claim and the maxLength headroom — thumbs-upped those.

Comment thread scripts/post-code.sh
PR_BODY_FROM_RESULT=""
if [ -n "${RESULT_FILE}" ]; then
if ! PR_BODY_FROM_RESULT="$(jq -r '.pr_body // empty' "${RESULT_FILE}" 2>/dev/null)"; then
echo "::notice::pr_body not found in result file; using commit body"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[minor] Heads up — this fires on jq parse errors, not on a missing pr_body field. .pr_body // empty exits 0 with empty output when the field is absent, so that case falls through silently. Could trip someone up debugging why pr_body was ignored.

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

Labels

requires-manual-review Review requires human judgment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Code agent should follow the target repo's PR template

4 participants