Skip to content

feat(#6966)!: flatten rejected status, replace stale findings on ok, mount review repo read-only - #7243

Closed
waynesun09 wants to merge 16 commits into
mainfrom
agent-6966-post-status-sanitize
Closed

waynesun09 wants to merge 16 commits into
mainfrom
agent-6966-post-status-sanitize

Conversation

@waynesun09

@waynesun09 waynesun09 commented Sep 11, 2026 •

Copy link
Copy Markdown
Member

Follow-up to #6972, from a review finding on fullsend-ai/agents#1167.

This PR does three things

  1. fix: flatten a rejected status before the generated post-script logs it. Commits flatten a rejected status… through log the rejected status with printf….
    • jq -r turns JSON \n/\r escapes into real line breaks. A crafted status could therefore split the runner's log line and start a new one with a :: workflow-command prefix.
    • The value is now flattened (CR/LF become spaces), capped at 40 characters, and written with printf '%s\n'. Unlike echo under xpg_echo, printf never expands backslash escapes.
  2. feat: agent new generates readonly_repo: true for the review role. Commits generate readonly_repo… and pin readonly_repo….
    • This matches the fleet's review harness, so a generated review agent's "do not edit files" is enforced by the sandbox, not just asked for in the prompt.
  3. feat + behaviour fix: on status: ok, replace stale findings with the all-clear. Commits on ok, replace an earlier findings comment… through pin the identity lookup's cancellation….
    • Before this PR, an ok result posted nothing. A findings comment from an earlier push therefore stayed on the pull request after a later push fixed the problem.
    • That fix needs a way to update a comment without ever creating one, which is the new fullsend issues post-comment --only-if-exists flag. It's documented in docs/guides/user/issues-commands.md and docs/guides/dev/cli-internals.md.
    • Under --only-if-exists, the earlier comment must carry the marker and have, as its author, exactly the login fullsend posts as. That login comes from forge.Client.GetAuthenticatedUser, which also resolves App installation tokens through GraphQL viewer. Login shape (such as a [bot] suffix) is never used.
    • A comment someone else plants with the marker is ignored.
    • The generated script checks for the flag (post-comment --help) and keeps the old "post nothing" behaviour on a runner whose pinned fullsend predates it.
    • The same rule now applies to every issues post-comment call on GitHub and GitLab (never edit a marker comment…). Before, the findings path edited whichever comment carried the marker first. A planted comment would receive the findings, and the all-clear could then never replace them.
    • Now a planted comment is ignored and a new comment is posted. Jira, whose markers are comment properties, is unchanged.
    • If the poster's identity can't be resolved, the lookup is retried briefly (two retries). If it still fails, the command fails with an error naming the cause and posts or edits nothing, with or without --only-if-exists. Posting a new comment instead would orphan the earlier one, whose stale findings no later run would update. The generated post-script runs under set -e, so the run fails visibly and a rerun recovers.
    • The help text and the user guide's trust model drop the "trusted CI" caveat.
    • sticky.Post still falls back to marker-only matching when identity lookup fails; that is sticky.Post: never edit a marker comment when the poster's identity cannot be resolved #7884.

fullsend-ai/agents#1167's examples/link-check/ already ships all three changes verbatim from this generator, so this PR must merge before agents#1167.

Release note

  • New flag: fullsend issues post-comment --only-if-exists updates the poster's own earlier marker comment and never creates one.
  • issues post-comment no longer edits a marker comment it can't verify as its own; it posts a new one instead, and it fails instead of posting when it can't verify its own identity.
  • fullsend agent new --role review now generates readonly_repo: true.
  • Generated post-scripts flatten a rejected status before logging it, and replace an earlier findings comment with the all-clear on an ok result.

BREAKING CHANGE

fullsend issues post-comment (GitHub, GitLab):

  1. Fails when it cannot resolve the identity it posts as. It retries briefly, then exits non-zero with an error naming the cause, posting and editing nothing. Before, it never failed for this reason.
  2. No longer edits a marker comment written by a different identity. After a token rotation, a PAT → GitHub App migration or another identity change, the next run posts a new comment instead of updating the old one.
  3. --dry-run resolves the identity too. A dry run now fails if it cannot resolve the posting identity. Before, it needed no identity at all.

Jira is unchanged. The deprecated fullsend post-comment (sticky.Post) is unchanged here; #7884 tracks bringing it to the same rule.

Tests

  • internal/agentnew/postscript_exec_test.go:
    • A status with an LF, CR or CRLF line break followed by ::error::injected is rejected on a single log line.
    • With bash -O xpg_echo, a literal backslash-n stays on one line.
    • The ok path is covered in dry run, through a stubbed fullsend that has the flag, and through a stubbed fullsend that lacks it.
  • internal/cli/issues_test.go (normal path and --only-if-exists):
    • Edits the poster's own comment.
    • Ignores a planted marker comment from another user or a look-alike login (mallory, evil-review[bot], fullsend-ai-review, x-fullsend-ai-review[bot]). The normal path posts a new comment; --only-if-exists posts nothing.
    • --only-if-exists creates nothing when no earlier comment exists, and skips a planted comment listed before the poster's own.
    • A transient identity-lookup failure followed by success retries and edits once, creating no second comment.
    • A persistent failure returns an error that names the cause and suggests a rerun, and creates or edits nothing. An empty login counts as a failure too.
    • Jira follows the same create/update rules under --only-if-exists.
    • Verified with Actions' GITHUB_TOKEN: GraphQL viewer.login == the posted comment's author == github-actions[bot].
  • roles_test.go: TestReviewRoleMountsTheRepositoryReadOnly.
  • Each of these mutations fails at least one test:
    • removing the CR/LF flattening, or flattening LF only
    • printf → echo
    • dropping the author check
    • a [bot]-suffix author match
    • an author-ends-with-self match
    • falling back to marker-only matching, or assuming the bot login, when the login can't be resolved
    • posting a new comment when the login can't be resolved
    • dropping the retry
    • removing the empty-login guard
  • Goldens are regenerated. Their diff is the template change only.
go test -race ./internal/agentnew/ ./internal/cli/ ./internal/sticky/ ./internal/tracker/

Part of #6966. Closes #7886.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Sanitize rejected post-script statuses before logging

🐞 Bug fix 🧪 Tests 🕐 10-20 Minutes

Grey Divider

AI Description

• Flatten and truncate invalid model statuses before writing runner errors.
• Prevent newline-based workflow-command injection through generated post-script logs.
• Add executable regression coverage and refresh generated-script goldens.
Diagram

graph TD
  A["Result JSON"] --> B["jq Parsing"] --> C{"Status Valid?"}
  C -- Yes --> D["Post Actions"]
  C -- No --> E["Flatten and Cap"] --> F["Single-line Error"]
Loading
High-Level Assessment

The localized template sanitization is appropriate because it neutralizes CR/LF at the exact stderr boundary while preserving useful diagnostic context. Omitting the rejected value would be safer but less informative, while introducing a shared sanitizer would add unnecessary abstraction for this single constrained field. Executable regression coverage plus regenerated goldens adequately protects generated output.

Files changed (4) +55 / -3

Bug fix (1) +8 / -1
post.sh.tmplSanitize invalid statuses before logging +8/-1

Sanitize invalid statuses before logging

• Changes the invalid-status branch to replace CR/LF characters with spaces and truncate displayed model output to 40 characters. This prevents crafted statuses from creating new runner log lines or workflow commands.

internal/agentnew/templates/post.sh.tmpl

Tests (3) +47 / -2
postscript_exec_test.goAdd workflow-command injection regression coverage +31/-0

Add workflow-command injection regression coverage

• Adds an executable post-script test using an invalid status containing a newline and '::error::' prefix. It verifies rejection fails as expected, emits no command-prefixed line, and remains a single stderr line.

internal/agentnew/postscript_exec_test.go

triage.txtRefresh triage post-script golden +8/-1

Refresh triage post-script golden

• Regenerates the triage fixture with the new invalid-status flattening and truncation behavior.

internal/agentnew/testdata/golden/triage.txt

validation-loop.txtRefresh validation-loop post-script golden +8/-1

Refresh validation-loop post-script golden

• Regenerates the validation-loop fixture with the sanitized invalid-status rejection path.

internal/agentnew/testdata/golden/validation-loop.txt

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:25 PM UTC · Completed 7:37 PM UTC

Commit: e45f4df · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $2.87

@qodo-code-review

qodo-code-review Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Escaped newlines can forge runner logs ✗ Dismissed
Description
shown strips only actual carriage returns and line feeds, but the following echo interprets
backslash escapes whenever Bash starts with the xpg_echo option. Because harness runner variables
flow into the post-script environment, BASHOPTS=xpg_echo makes a status containing literal
\n::error::... materialize a second workflow-command line after the check.
Code

internal/agentnew/templates/post.sh.tmpl[R87-88]

+    shown="${status//[$'\r\n']/ }"
+    echo "post-__AGENT_NAME__: status must be ok, findings or error (got '${shown:0:40}')" >&2
Relevance

●●● Strong

Model-controlled log content can still forge workflow commands; hardening output with printf or
escaping matches this security-focused change.

PR-#6972

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The template explicitly uses Bash and passes the sanitized value through echo. Runner environment
configuration is merged into the post-script environment without filtering BASHOPTS, so the
supported xpg_echo setting causes echo to convert an otherwise untouched literal \n into a
real line break.

internal/agentnew/templates/post.sh.tmpl[1-1]
internal/agentnew/templates/post.sh.tmpl[81-90]
internal/harness/harness.go[280-283]
internal/cli/run.go[1001-1014]
internal/cli/run.go[4002-4041]
internal/cli/run.go[4067-4077]

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 sanitized model-supplied status is printed with Bash `echo`, which can interpret literal backslash escapes when `xpg_echo` is enabled and recreate a newline after CR/LF removal.

## Fix Focus Areas
- internal/agentnew/templates/post.sh.tmpl[87-88]
- internal/agentnew/postscript_exec_test.go[139-163]

## Recommended Fix
Replace `echo` with `printf '%s\n'` using the complete diagnostic as a `%s` argument, then add a regression case that runs with `BASHOPTS=xpg_echo` and supplies a literal `\\n::error::injected` status. Regenerate the golden files after updating the template.

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



Remediation recommended

2. Local tests require an undeclared tool ✓ Resolved
Description
TestGeneratedPostScriptFlattensAnInvalidStatusBeforeLogging calls runPostScript without the
exec.LookPath("jq") guard used by the neighboring execution test. When jq is absent, the generated
script exits before validating the status and the test fails its stderr assertion, turning an
optional external prerequisite into a package-test failure.
Code

internal/agentnew/postscript_exec_test.go[148]

+	_, stderr, err := runPostScript(t, script, runDir)
Relevance

●●● Strong

Optional jq dependency makes this test nondeterministically fail; nearby accepted precedent favors
explicit preflight checks.

PR-#6972

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The helper launches the generated Bash script, which invokes jq while reading the result. The
existing execution test explicitly skips without jq, whereas the newly added test reaches the helper
without performing that check.

internal/agentnew/postscript_exec_test.go[34-50]
internal/agentnew/postscript_exec_test.go[75-79]
internal/agentnew/postscript_exec_test.go[139-153]
internal/agentnew/templates/post.sh.tmpl[71-79]

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

## Issue description
The new generated-script execution test runs an external `jq` command unconditionally even though the neighboring test treats that tool as optional.

## Fix Focus Areas
- internal/agentnew/postscript_exec_test.go[139-148]

## Recommended Fix
Add the same `exec.LookPath("jq")` check and `t.Skip` used by the neighboring execution test, or move that prerequisite check into a shared helper used by every generated-script test.

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


3. Agent example remains injectable ✗ Dismissed
Description
post.sh.tmpl sanitizes rejected status, but the generated fullsend-ai/agents link-check
post-script still echoes the same model-controlled value raw. When that configured example receives
a status containing a newline followed by ::, its post-script writes the injected workflow command
at the start of a runner-log line.
Code

internal/agentnew/templates/post.sh.tmpl[R87-88]

+    shown="${status//[$'\r\n']/ }"
+    echo "post-__AGENT_NAME__: status must be ok, findings or error (got '${shown:0:40}')" >&2
Relevance

●● Moderate

The external generated example remains risky, but remediation depends on regeneration in another
repository after this PR merges.

PR-#6972

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR template flattens and truncates invalid status values before logging, while the agents
repository's generated copy still interpolates the raw status. The agents harness configures that
copy as its post-script, and its tests document that the script consumes untrusted model output and
turns it into work-item comments.

internal/agentnew/templates/post.sh.tmpl[81-90]
External repo: fullsend-ai/agents, examples/link-check/scripts/post-link-check.sh [76-84]
External repo: fullsend-ai/agents, examples/link-check/harness/link-check.yaml [30-35]
External repo: fullsend-ai/agents, scripts/example-link-check-test.sh [2-7]

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 generated link-check post-script in `fullsend-ai/agents` still logs an invalid model-supplied status without flattening CR/LF, leaving the reference agent exposed to workflow-command injection.

## Fix Focus Areas
- internal/agentnew/templates/post.sh.tmpl[83-89]
- examples/link-check/scripts/post-link-check.sh[81-84]
- scripts/example-link-check-test.sh[94-127]

## Recommended Fix
Coordinate an agents-repository update that regenerates `examples/link-check/scripts/post-link-check.sh` from this template and adds the multiline malicious-status regression case to `scripts/example-link-check-test.sh`. Merge or release that update with this change so the executable reference example receives the same sanitization.

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


Grey Divider

Context sources
✅ Compliance rules (platform): 71 rules
✅ Cross-repo context — repo relationships
  Explored: repo: fullsend-ai/agents (branch: agent-6966-agent-templates, sha: c761713c)
Review mode: ⚖️ Balanced: This is a security-relevant runtime shell-script change affecting workflow-log injection behavior, warranting a complete single-pass review despite its localized scope.

Grey Divider

Tip of the day
💡 Did you know, you can keep summaries lean with Findings visible per group, which tucks the rest behind a View link

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread internal/agentnew/templates/post.sh.tmpl Outdated
Comment thread internal/agentnew/postscript_exec_test.go Outdated
Comment thread internal/agentnew/templates/post.sh.tmpl Outdated
@codecov

codecov Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.45283% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/cli/issues.go 95.74% 1 Missing and 1 partial ⚠️
internal/tracker/forge_client.go 0.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

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

fullsend-ai-review Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

Risk Assessment: moderate (2/5)

Details

Anchored at moderate risk as Tier 1 signals remain unchanged with zero protected, security, CI, or dependency modifications and 31% test ratio, where large change size (16 files, 898 lines) and elevated git churn are balanced by trusted authorship, complete issue acceptance criteria coverage, and now-explicit breaking-change flagging.

Previous run

Risk Assessment: moderate (2/5)

Details

Although PR growth crossed into larger size buckets (16 files, 898 lines), the change retains zero protected/security/CI/dependency modifications, trusted authorship, and solid test coverage (31%), anchoring the score at moderate (2) alongside elevated git history churn and unflagged comment-editing behavior modifications.

Previous run (2)

Risk Assessment: moderate (2/5)

Details

A well-tested, narrowly-scoped bug fix (33% test-file ratio, no protected/security/CI/dependency paths flagged, no author-risk signals) touching files with moderate churn and a history of fix/revert commits, referencing an already-closed tracking issue and modifying existing template/generator behavior without a rollback flag; these moderate git-history and rollback-safety signals offset the otherwise low metadata risk, yielding a moderate composite score.

Previous run (3)

Risk Assessment: moderate (2/5)

Details

Anchored re-review: despite the diff growing ~3.8x since the prior assessment (136->512 lines, 9->14 files) and crossing FILES_CHANGED/LINES_CHANGED buckets, BLAST_RADIUS was already maxed pre-growth so the Tier 1 composite is essentially unchanged; combined with stable Tier 2 churn/author signals and a well-tested, narrowly-scoped security-hardening fix with no protected paths, dependency changes, or CI changes, the weighted composite preserves the prior moderate score of 2/5.

Previous run (4)

Risk Assessment: moderate (2/5)

Details

Weighted composite (~1.54, rounded to 2): small, well-tested, non-security-flagged hardening fix in an actively-churning but single-author-dominated new package, addressing a narrow slice of a larger epic; consistent with the PR's own risk/moderate label.

Previous run (5)

Risk Assessment: moderate (2/5)

Details

Tier 1 signals are unchanged from the prior three assessments on this PR (still a small/medium 9-file, 136-line change, no protected/security/CI/dependency paths, moderate test ratio, non-bot returning contributor), Tier 2 shows only moderate churn/fix-revert history concentrated in one docs file and the previously flagged render_test.go coupling gap is now closed by this PR, and Tier 3 remains unavailable (linked issue closed, no actionable scope), so per re-review anchoring the composite stays at the prior moderate score of 2/5.

Previous run (6)

Risk Assessment: moderate (2/5)

Details

Re-scoring the complete 8-file/130-line PR reproduces the same qualitative Tier 1 profile as prior reviews -- small-to-medium blast radius, no protected/security/CI/dependency paths, moderate 0.25 test ratio, non-bot returning contributor -- and Tier 2 shows only modest churn/fix-revert history on the touched golden/template files; with no acceptance-criteria-bearing linked issue to score (Tier 3 unavailable, weights redistributed 62/38 to Tier 1/Tier 2), the weighted composite rounds to a moderate score of 2, unchanged from prior assessments.

Previous run (7)

Risk Assessment: moderate (2/5)

Details

Re-review at head c2f4acd (103 lines vs. the prior 58-line commit, same 4-file set) reproduces the same Tier 1 profile as the prior assessment - small blast radius, no protected/security/CI/dependency paths, moderate test ratio, non-bot returning contributor - and Tier 2 shows only moderate churn/fix-revert history on the touched golden/template files with one coupled file (render_test.go) absent from the PR; with no acceptance-criteria-bearing linked issue to score (Tier 3 unavailable, weights redistributed 62/38 to Tier 1/Tier 2), the weighted composite again rounds to a moderate score of 2, unchanged from the prior review.

Previous run (8)

Risk Assessment: moderate (2/5)

Details

Small, self-contained security-hardening fix (4 files, 58 lines, no protected/security-sensitive/CI/dependency paths, 0.25 test ratio, includes a targeted regression test) pulls Tier 1 low; moderate Tier 2 churn/fix-revert history on the touched golden/template files plus a coupled file not included in the PR, and a Tier 3 pull-up from lack of a rollback flag for this behavior change combine to a moderate composite score of 2.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

Review

Findings

High

  • [sub-agent-failure] N/A — The correctness sub-agent did not return findings: it timed out (900s) on a full-scope dispatch and a retried, narrowed-scope dispatch (limited to internal/cli/issues.go, internal/sticky/sticky.go, internal/tracker/forge_client.go) in this run — the same pattern (full-scope + narrowed retry, both timing out) also occurred in the immediately prior review run. This PR modifies safety-critical identity-verification and comment-authorization logic (internal/cli/issues.go, internal/sticky/sticky.go, internal/tracker/forge_client.go) that a correctness pass has now failed to independently verify across two consecutive review runs.

Medium

  • [fail-open] internal/cli/issues.go:352 — The new author-identity verification and fail-closed behavior this PR adds to fullsend issues post-comment covers the GitHub/GitLab path (postTrackerStickyComment, which calls resolveTrackerSelf and requires findMarkedTrackerComment's author match before treating a comment as existing) but not the Jira path (postJiraStickyComment). There, existing := jc.FindCommentByMarkerProperty(jiraComments, cfg.Marker) still matches on the marker property alone, with no author/identity check, and the --only-if-exists branch for Jira inherits the same marker-only lookup. This is the same comment-spoofing shape (a planted marker-bearing comment treated as the bot's own) that this PR's GitHub/GitLab change was built to close, left open on the one tracker this PR did not update. The PR body states "Jira, whose markers are comment entity properties, is unchanged" as if comment-property markers made the identity question moot, but a marker property is set by whoever posts the comment, including a non-bot actor, so the same planted-comment risk applies.
    Remediation: Apply the same identity gate on the Jira sticky path: resolve the authenticated Jira user (or document why Jira's comment-property markers are not forgeable the way GitHub/GitLab comment bodies are, if that is actually true), and require both the marker property and author to match before editing or before --only-if-exists treats a comment as existing.

  • [scope-bundling] internal/agentnew/templates/post.sh.tmpl:88 — The PR bundles three distinct changes under the composite title feat(#6966)!: flatten rejected status, replace stale findings on ok, mount review repo read-only: (1) a security-hardening bug fix flattening an invalid status value to prevent GitHub Actions workflow-command injection in runner logs (this file, line 88), (2) a generator feature adding readonly_repo: true for the review role (tracked under feat(cli): fullsend agent new — generate a complete custom agent from minimal parameters #6966), and (3) a new CLI flag (--only-if-exists) plus a breaking identity-verification trust-model change in issues post-comment (tracked under issues post-comment: verify the comment author before editing, and fail closed on an unresolvable identity #7886, now correctly linked and marked breaking). Linking issues post-comment: verify the comment author before editing, and fail closed on an unresolvable identity #7886 resolves the prior scope-authorization-expansion concern, but the bundling itself persists: GoReleaser derives release notes directly from merged PR titles per COMMITS.md, and under this single feat(#6966)!:-prefixed title the workflow-command-injection fix is categorized under Features rather than Bug Fixes, and the breaking-change marker is scoped to the closed #6966 epic rather than to the actual breaking surface (issues post-comment / issues post-comment: verify the comment author before editing, and fail closed on an unresolvable identity #7886).
    Remediation: Consider splitting into focused PRs matching Conventional Commit types and scopes in future work: a fix PR for the post-script log-injection hardening, a feat(#6966) PR for the review role's readonly_repo generation, and a feat(issues)! PR closing issues post-comment: verify the comment author before editing, and fail closed on an unresolvable identity #7886 for the issues post-comment trust-model overhaul and --only-if-exists flag.

Low

  • [breaking-cli] internal/cli/issues.go:423 — resolveTrackerSelf runs unconditionally in postTrackerStickyComment, before the cfg.DryRun check further down. So --dry-run is now also subject to the new fail-closed identity-resolution requirement — previously this code path performed no identity verification at all, so --dry-run never had this failure mode. The PR's ## BREAKING CHANGE section describes the fail-closed behavior in general terms but does not call out --dry-run specifically, even though dry-run is a common mode for CI smoke-testing/preview pipelines that may now break on token/permission issues that previously didn't matter for a read-only preview.
    Remediation: Mention in the BREAKING CHANGE notes that --dry-run is also subject to the new identity-resolution requirement, since callers may rely on dry-run succeeding without full posting permissions.

  • [sub-agent-failure] N/A — The challenger adversarial pass was not dispatched: time budget exhausted (approximately 394s remaining after two rounds of correctness/security dispatches, two of which timed out at 900s each). Using the pre-challenger merged finding set without adversarial review.


Next steps:

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

Review

Findings

High

  • [sub-agent-failure] N/A — The correctness sub-agent did not return findings: it timed out (900s) on both the initial full-scope dispatch and a retried, narrowed-scope dispatch. This PR touches safety-critical identity-verification and comment-authorization logic (internal/cli/issues.go, internal/sticky/sticky.go) and generated-script injection hardening (internal/agentnew/templates/post.sh.tmpl) that a correctness pass could not independently verify in this run.

  • [sub-agent-failure] N/A — The security sub-agent did not return findings: it timed out (900s) on both the initial full-scope dispatch and a retried, narrowed-scope dispatch. This PR claims to fix a prior low-severity authorization finding (unauthenticated marker-comment matching in internal/cli/issues.go) and introduces new identity-verification/fail-closed logic; that fix could not be independently verified by a security pass in this run.

  • [breaking-change-unmarked] internal/cli/issues.go:420 — fullsend issues post-comment now unconditionally requires resolving the posting identity via a new authenticated-user lookup (with retries) before editing or creating a comment on GitHub/GitLab, and hard-fails the whole command if that identity cannot be resolved — behavior the prior implementation never had (it never made this call and always succeeded as long as the forge write call succeeded). It also tightens comment matching from "marker present" to "marker present AND author == resolved self," so a comment previously edited across a bot-identity change (token rotation, PAT→GitHub App migration, mint identity change) will now be left alone and a duplicate comment created instead. This is a backward-incompatible change to an existing, externally-consumed CLI surface — the PR body itself states fullsend-ai/agents's generated post-scripts and other CI callers depend on issues post-comment — with no opt-out flag for existing callers. Per this repo's AGENTS.md, breaking changes (default-behavior changes, validation that rejects previously-accepted input) must carry the ! suffix in both commit messages and the PR title, and "a missing ! is an important-severity review finding." The current PR title ("feat(feat(cli): fullsend agent new — generate a complete custom agent from minimal parameters #6966): flatten rejected status, replace stale findings on ok, mount review repo read-only") carries no ! and no commit/PR body contains a BREAKING CHANGE: trailer.
    Remediation: Add the ! suffix to the PR title and the relevant commit(s) (e.g. feat(#6966)!: ...) and add a BREAKING CHANGE: trailer/section describing: (1) issues post-comment now requires the forge client to support an authenticated-identity lookup and fails the run if that lookup fails, where it previously never failed for this reason; (2) comments previously edited solely by marker match may now be treated as foreign and left alone, causing a new comment to be created instead.

Medium

  • [scope-authorization-expansion] internal/cli/issues.go:246 — This PR links to closed parent epic feat(cli): fullsend agent new — generate a complete custom agent from minimal parameters #6966 ("fullsend agent new — generate a complete custom agent") and frames itself as a follow-up to PR feat(#6966): add fullsend agent new to generate a complete custom agent #6972. While the internal/agentnew/ template/role changes trace directly to feat(cli): fullsend agent new — generate a complete custom agent from minimal parameters #6966, adding the general-purpose --only-if-exists flag to fullsend issues post-comment and altering the global comment-authorization trust model in internal/cli and internal/sticky modifies core CLI/tracker infrastructure shared across all workflows, which goes beyond what the closed epic authorizes.
    Remediation: File a dedicated issue authorizing the --only-if-exists flag and the author-verification trust-model change for issues post-comment, or split the PR so internal/agentnew/ changes trace to feat(cli): fullsend agent new — generate a complete custom agent from minimal parameters #6966 while the internal/cli/internal/sticky changes are tracked separately.

  • [scope-bundling] internal/agentnew/templates/post.sh.tmpl:88 — The PR bundles three distinct changes under one title: (1) a security-hardening fix flattening an invalid status value to prevent GHA workflow-command injection in runner logs, (2) a feature adding readonly_repo: true to the generated review role, and (3) a new CLI flag plus a breaking identity-verification behavior change in issues post-comment. Filing the injection fix under a composite feat title puts it in "Features" rather than "Bug Fixes" in the GoReleaser-generated release notes.
    Remediation: Consider splitting into focused PRs matching Conventional Commit types/scopes: a fix(#6966) for the status-flattening hardening, a feat(#6966) for readonly_repo, and a feat(cli)!: for --only-if-exists plus the breaking trust-model change.

  • [api-consistency] internal/sticky/sticky.go:48 — The forge-based sticky-comment primitive (sticky.Post, used by post-review and other forge-facing commands) still treats a failed identity lookup as merely "degraded" (logs a warning, falls back to marker-only matching), while the newly hardened tracker-based primitive used by issues post-comment now hard-fails the whole command on the same failure. Two sticky-comment code paths in this repo, both reachable by external automation, now diverge on how they handle an unresolvable bot identity.
    Remediation: Either align sticky.Post's trust model with the stricter one (as a documented part of this same breaking change), or explicitly document in both commands why the two primitives differ so downstream integrators aren't surprised when one fails hard and the other silently degrades under the same forge-token conditions.

Low

  • [interface-evolution] internal/tracker/forge_client.go:30 — The new authenticated-identity method is added only to the concrete client type and detected at runtime via an unexported resolver-interface type assertion in internal/cli/issues.go, rather than being added to the tracker.Client interface itself. This is the correct non-breaking approach for existing implementations, but any future tracker.Client implementation that doesn't happen to implement this method will silently fall into the "cannot verify identity" fail-closed path for --only-if-exists and default post-comment edits, with no compile-time signal.
    Remediation: No action required for this PR; consider a one-line note in the Go Code / Forge Abstraction docs that tracker.Client implementations should implement the authenticated-identity method to retain edit/--only-if-exists support.

  • [sub-agent-failure] N/A — The challenger adversarial pass was not dispatched: time budget exhausted (well under 600s remaining after two rounds of timed-out correctness/security dispatches). Using the pre-challenger merged finding set without adversarial review.


Next steps:

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

Review

Findings

Medium

  • [scope-authorization-tier-mismatch] internal/cli/issues.go:246 — The PR is titled fix(#6966): flatten a rejected status before the generated post-script logs it, but the diff also introduces a new, user-invokable CLI flag: fullsend issues post-comment --only-if-exists (registered at internal/cli/issues.go:246), plus the accompanying stale-findings-replacement behavior in the generated post.sh.tmpl. Per this repository's COMMITS.md, "a new CLI command or flag they can invoke" is squarely a feat, not a fix, and COMMITS.md explicitly instructs reviewers to "check that commit messages and PR titles use the correct type prefix... Flag violations as a required change — they are not cosmetic," because GoReleaser builds release notes exclusively from merged PR titles. A fix-typed title files the new flag under Bug Fixes instead of Features.
    Remediation: Retitle the PR (and, if separate, the merge commit) with a feat-first framing, e.g. feat(#6966): add --only-if-exists to issues post-comment and flatten rejected status in post-script, or split the security fix (log/workflow-command injection flattening) into its own fix(#6966) PR separate from the new --only-if-exists flag and stale-comment-replacement feature.

Low

  • [authorization] internal/cli/issues.go:536 — The GitHub/GitLab sticky-comment lookup used by fullsend issues post-comment (findMarkedTrackerComment, internal/cli/issues.go:534-541, called from postTrackerStickyComment) matches the first comment whose body contains the marker string and does not check tracker.Comment.Author. This is not equivalent to sticky.FindMarkedComment (internal/sticky/sticky.go), which is passed the authenticated bot login and skips non-bot authors. The command's existing help text already documents this gap as an accepted trust-model trade-off for trusted CI use, so it predates this PR. This PR extends that same unfiltered lookup to a new purpose: the ok-path "does an earlier findings comment exist" check behind the new --only-if-exists flag. The new comment added in post.sh.tmpl around the --only-if-exists call states the marker is "found through the forge client and the bot's own author" — that claim does not hold for this CLI code path. A PR participant who plants a comment containing the exact marker string before the bot's first post could have it treated as "the existing comment": on the ok path this can suppress the intended all-clear replacement (leaving the bot's real findings comment stale — the exact bug this PR sets out to fix) or cause the bot to PATCH a third party's comment. The new --only-if-exists tests only seed bot-authored comments and do not exercise a spoofed/non-bot marker.
    Remediation: Filter findMarkedTrackerComment's result by the authenticated bot login, mirroring sticky.FindMarkedComment. tracker.Comment already carries Author; add a way to obtain the authenticated user for tracker clients (or route through forge.Client.GetAuthenticatedUser where available) and skip non-matching authors. Fail closed (do not treat a non-bot comment as "existing") when the bot identity cannot be resolved. Add a test where a non-bot comment containing the marker is listed first and confirm --only-if-exists does not treat it as an existing match. Also correct the post.sh.tmpl comment so it does not claim bot-author filtering that this code path does not perform.

Next steps:

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

Looks good to me

Previous run (4)

Review

Findings

Low

  • [incomplete-doc] docs/guides/dev/cli-internals.md:152 — This PR introduces the --only-if-exists flag on fullsend issues post-comment and documents it in docs/guides/user/issues-commands.md, but docs/guides/dev/cli-internals.md — which docs/contributing/documentation.md lists as a touchpoint for the issues command group and describes as "the most comprehensive single reference for all commands (including minor ones)" — was not updated. Its post-comment command-tree block (lines 146-152) lists --tracker, --project, --number, --marker, --keep-history, and --fullsend-dir but not the new flag. (The same block is also missing several pre-existing flags such as --dry-run, so this is a pre-existing partial-catalog issue that the new flag simply falls into, not a regression specific to user-facing behavior — hence low rather than medium.)
    Remediation: Add a --only-if-exists row to the post-comment flags list under issues in docs/guides/dev/cli-internals.md (around line 152), matching the format of the other flag rows.

Next steps:

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

Review

Findings

High

  • [logic-error] internal/agentnew/templates/post.sh.tmpl:135 — On the status=ok live path, the new existence check is gh api --paginate "repos/${REPO}/issues/${NUMBER}/comments" --jq --arg m "${MARKER}" '[.[] | select(.body | contains($m))] | length'. gh api --jq takes a single query string and has no jq --arg flag; verified against the live gh CLI, this invocation fails with accepts 1 arg(s), received 4. The call sits inside a process substitution (while read -r n; do ...; done < <(gh api ...)), so its non-zero exit is invisible to set -euo pipefail — the loop reads nothing, earlier stays 0, and the script prints "nothing to post" and exits 0. The "stale findings on a later push" replacement this PR's body advertises can therefore never fire in the generated script as written; it silently falls back to the old behavior every time. The same invocation also aggregates --paginate --jq '...|length' per page rather than across pages — the exact anti-pattern docs/contributing/shell-scripting.md tells reviewers to flag. The only new test, TestGeneratedPostScriptOkPostsNothingWithoutAnEarlierComment, runs under dry run and returns before this call executes; by its own comment, the live path is deferred to the fullsend-ai/agents example test, so this repo's suite cannot catch the bug. It also ships into testdata/golden/triage.txt and testdata/golden/validation-loop.txt — every agent fullsend agent new generates.
    Remediation: Do not pass jq's --arg to gh api --jq, and do not aggregate with --paginate --jq '...|length'. Prefer routing through fullsend issues get (already returns comments JSON via forge.Client); if a raw-gh rewrite is kept, collect pages first then aggregate once, e.g. earlier=$(gh api --paginate "repos/${REPO}/issues/${NUMBER}/comments" | jq -s --arg m "${MARKER}" 'add | [.[] | select(.body | contains($m))] | length'), and ensure a gh/jq failure actually fails the script instead of being swallowed. Add an exec test with a stub gh on PATH covering both 0-matches and >0-matches, and regenerate the two golden files.

Medium

  • [forge-abstraction-violation] internal/agentnew/templates/post.sh.tmpl:135 — The new stale-comment check calls gh api --paginate "repos/${REPO}/issues/${NUMBER}/comments" directly. docs/contributing/forge-abstraction.md requires new GitHub API operations in CI-facing bash scripts to go through a fullsend CLI subcommand backed by forge.Client, not inline gh api calls, and instructs reviewers to flag violations at medium-or-higher. This template previously only posted via fullsend issues post-comment (itself forge.Client-backed); this is the first inline gh api call it introduces, even though fullsend issues get already lists comments via the same abstraction. See also: [logic-error] at the same line — the fix for that finding (routing through fullsend issues get) also resolves this one.
    Remediation: Replace the raw gh api --paginate call with fullsend issues get --tracker github --project "${REPO}" --number "${NUMBER}" (or a small purpose-built existence-check subcommand/flag) piped to jq, so the check goes through forge.Client like the rest of the script.

Low

  • [authorization] internal/agentnew/templates/post.sh.tmpl:136 — The new ok-path existence check counts any issue comment whose body contains(${MARKER}), with no author filter. internal/sticky/sticky.go's FindMarkedComment (used by the fullsend issues post-comment call later in this same script) deliberately filters by bot author to block marker spoofing. A spoofed marker cannot make the sticky helper edit a third party's comment — Post still filters by bot login before updating — but it could make this pre-check believe an earlier findings comment exists and fall through to post-comment, creating an unwanted extra all-clear comment. This is currently latent behind the [logic-error] syntax bug at line 135, which prevents the check from running at all.
    Remediation: Scope the existence check the same way FindMarkedComment does: filter by the authenticated bot login in addition to contains($m), or reuse fullsend issues get/a small helper that already resolves the bot user via forge.Client.

Next steps:

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

Looks good to me

Previous run (7)

Review

Findings

Low

  • [missing-test] internal/agentnew/roles_test.go:135 — TestReviewRoleMountsTheRepositoryReadOnly only asserts the in-memory roleTable struct field (role.ReadonlyRepo). It does not verify that render.go's buildHarness actually copies role.ReadonlyRepo onto the emitted harness.Harness / rendered harness/<name>.yaml. The existing TestRoleImageReachesTheHarness in render_test.go — which Render()s each role and harness.Load()s the result to verify Image/Providers/Profiles survive into the generated YAML — was not extended to also assert ReadonlyRepo. A regression that dropped ReadonlyRepo: role.ReadonlyRepo from buildHarness would pass every existing and new test in this PR, and the generated review harness would silently ship writable, so the prompt's "do not edit files" would be requested rather than enforced — the exact problem this PR sets out to close. (Independently raised by both the correctness and security review passes, and confirmed against the current code by the adjudication pass.)
    Remediation: Extend TestRoleImageReachesTheHarness (or add a parallel assertion) to check h.ReadonlyRepo == role.ReadonlyRepo (equivalently == (name == "review")) after Render + harness.Load, so the flag is pinned in the emitted YAML, not just on the Role struct.

Next steps:

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

Looks good to me

Previous run (9)

Review

Findings

Low

  • [test-adequacy] internal/agentnew/postscript_exec_test.go:139 — TestGeneratedPostScriptFlattensAnInvalidStatusBeforeLogging executes the generated post-script (which requires jq) but does not skip when jq is missing, unlike the sibling exec test TestGeneratedPostScriptRunsFromTheRunDirectory, which guards with exec.LookPath("jq"). Without that skip, environments lacking jq fail this test on the earlier "not a JSON object" path instead of exercising status flattening — a red result would not mean the behavior under test actually failed.
    Remediation: Add the same jq LookPath skip at the start of TestGeneratedPostScriptFlattensAnInvalidStatusBeforeLogging that TestGeneratedPostScriptRunsFromTheRunDirectory uses.

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.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 7:41 PM UTC · Ended 7:42 PM UTC

Commit: f987bca · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:44 PM UTC · Completed 7:58 PM UTC

Commit: c2f4acd · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $2.95

@fullsend-ai-review
fullsend-ai-review Bot dismissed their stale review September 11, 2026 19:58

Superseded by updated review

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added the ready-for-merge All reviewers approved — ready to merge label Sep 11, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 8:14 PM UTC · Ended 8:29 PM UTC

Commit: d940a8d · View workflow run →

@github-actions

github-actions Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

Site preview

Preview: https://6a06083c-site.fullsend-ai.workers.dev

Commit: 069e28455ba97c9112791c227a327b69648a2ab0

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review
fullsend-ai-review Bot dismissed their stale review September 11, 2026 20:29

Superseded by updated review

@fullsend-ai-review fullsend-ai-review Bot removed the ready-for-merge All reviewers approved — ready to merge label Sep 11, 2026
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:14 PM UTC · Completed 8:29 PM UTC

Commit: d940a8d · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $3.90

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:33 PM UTC · Completed 8:43 PM UTC

Commit: ae929ba · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $1.52

@fullsend-ai-review
fullsend-ai-review Bot dismissed their stale review September 11, 2026 20:42

Superseded by updated review

fullsend-ai-review[bot]

This comment was marked as outdated.

…s its own

issues post-comment (GitHub, GitLab) edited the first comment carrying the
marker, whoever wrote it. A planted marker comment therefore received the
findings, and the --only-if-exists all-clear, which checks the author,
could never replace them.

The poster's identity is now always resolved, and an existing comment is
edited only on an exact author and marker match; a planted one is ignored
and a new comment posted. When the identity cannot be resolved, no
comment is edited: a new one is posted with a warning naming the reason,
or nothing under --only-if-exists. An empty identity matches nothing by
construction. Jira, whose markers are comment properties, is unchanged.

Help text and the user guide's trust model drop the "acceptable in
trusted CI" caveat for the new rule.

BREAKING CHANGE: issues post-comment (GitHub, GitLab) no longer edits a marker
 comment written by a different identity; after a token rotation or App
 migration the next run posts a new comment instead.
Assisted-by: Claude (fix), Claude (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
…s comment

A comment with no author and an unresolved (empty) identity must not
match. The lookup's empty-identity guard was only covered indirectly.
Help text, the user guide and the generated script's comment now say an
existing comment is found by its marker and its own author.

Assisted-by: Claude (fix), Claude (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
…its identity

Posting a new comment when the poster's login could not be resolved
avoided editing an unverified comment, but left the earlier comment
behind: later runs keep updating the oldest own comment, so a findings
comment orphaned by one transient failure would never be cleared.

The identity lookup now retries briefly (two retries, context-aware) and,
if it still fails, the command returns an error naming the cause and
asking for a rerun, posting and editing nothing, with or without
--only-if-exists. The generated post-script runs under set -e, so the run
then fails visibly; its comment says so.

BREAKING CHANGE: issues post-comment now fails, posting nothing, when it
 cannot resolve the identity it posts as.
Assisted-by: Claude (fix), Claude (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
…y guard

A cancelled context stops the retry without waiting out the backoff, and
an empty identity matches no comment, not even an authorless one. The
--dry-run help now says it posts or edits nothing rather than "makes no
API calls": it lists comments and resolves the poster's identity.

Assisted-by: Claude (fix), Claude (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
@waynesun09 waynesun09 changed the title feat(#6966): flatten rejected status, replace stale findings on ok, mount review repo read-only feat(#6966)!: flatten rejected status, replace stale findings on ok, mount review repo read-only Oct 1, 2026
@waynesun09
waynesun09 force-pushed the agent-6966-post-status-sanitize branch from e340bce to 069e284 Compare October 1, 2026 02:05
@fullsend-ai-review

fullsend-ai-review Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:07 AM UTC · Completed 2:47 AM UTC

Commit: 069e284 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $3.57

@waynesun09
waynesun09 dismissed stale reviews from fullsend-ai-review[bot] and fullsend-ai-review[bot] October 1, 2026 02:25

Outdated: the findings are addressed at 069e284 (marked breaking, authorizing issue #7886), and all threads are resolved.

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

Note: The following review comments could not be posted on the diff (GitHub returned 422) and are included here instead:

  • internal/cli/issues.go (file-level): Line 352 · [medium] fail-open

The new author-identity verification and fail-closed behavior this PR adds to fullsend issues post-comment covers the GitHub/GitLab path (postTrackerStickyComment, which calls resolveTrackerSelf and requires findMarkedTrackerComment's author match before treating a comment as existing) but not the Jira path (postJiraStickyComment). There, existing := jc.FindCommentByMarkerProperty(jiraComments, cfg.Marker) at line 352 still matches on the marker property alone, with no author/identity check, and the --only-if-exists branch for Jira (line 380) inherits the same marker-only lookup. This is exactly the comment-spoofing shape (a planted marker-bearing comment being treated as the bot's own) that this PR's GitHub/GitLab change was built to close, left open on the one tracker this PR did not update. The PR body states "Jira, whose markers are comment entity properties, is unchanged" as if comment-property markers made the identity question moot, but a marker property is set by whoever posts the comment, including a non-bot actor, so the same planted-comment risk applies.

Suggested fix: Apply the same identity gate on the Jira sticky path: resolve the authenticated Jira user (or document why Jira's comment-property markers are not forgeable the way GitHub/GitLab comment bodies are, if that is actually true), and require both the marker property and author to match before editing or before --only-if-exists treats a comment as existing.

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

Comment thread internal/agentnew/templates/post.sh.tmpl
Comment thread internal/cli/issues.go
@waynesun09
waynesun09 dismissed fullsend-ai-review[bot]’s stale review October 1, 2026 03:50

Addressed at 069e284 without a code change: the scope thread was answered (split deliberately declined; BREAKING CHANGE names issues post-comment and Closes #7886), --dry-run was added as a breaking-change point in the PR body, and all threads are resolved.

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

PRs should contain only one change at a time.

@waynesun09

Copy link
Copy Markdown
Member Author

Thanks @rh-hemartin, fair point. This is now split into one change per PR:

Closing this one in favour of those. The branch stays until they merge.

@waynesun09 waynesun09 closed this Oct 1, 2026
waynesun09 added a commit to fullsend-ai/agents that referenced this pull request Oct 1, 2026
fullsend-ai/fullsend#7243 was split into #7914 (status sanitiser),
#7915 (readonly_repo), #7916 (comment-author check) and #7917
(--only-if-exists, stacked on #7916). Regenerating from all four merged
onto fullsend main gives output byte-identical to #7243's last head,
so only the reference changes.

Assisted-by: Claude (fix), Claude (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
waynesun09 added a commit to fullsend-ai/agents that referenced this pull request Oct 1, 2026
fullsend issues post-comment exits non-zero without posting when it
cannot resolve the login it posts as (fullsend-ai/fullsend#7243). The
post-script relies on set -e to surface that. A stub that lists
--only-if-exists but fails every post now checks that both the ok and
the findings path exit non-zero after attempting the post.

Assisted-by: Claude (fix), Claude (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
waynesun09 added a commit to fullsend-ai/agents that referenced this pull request Oct 1, 2026
The example's step 1 matched ISSUE_URL against a regex containing
`https://github[.]com/` inside the bash block. Fullsend's SSRF
PreToolUse hook, on by default for Bash, scans the command for URLs and
refuses that one as malformed, so an agent that followed the prompt
would hit the step's error branch and report an error on every pull
request. Use the REPO_FULL_NAME and ISSUE_NUMBER the harness now passes
(set for pull-request dispatches too) and drop the URL from the
command.

The README and the authoring skill now also tell users to replace the
generated prompt's `gh issue view`, which goes through GraphQL that the
generated profile does not allow (fullsend-ai/fullsend#7014), with the
REST equivalent.

post-link-check.sh is regenerated from fullsend-ai/fullsend#7243 at
e340bcefa: a comment-only change saying post-comment fails the run when
it cannot resolve its own login.

Assisted-by: Claude (fix), Claude (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
waynesun09 added a commit to fullsend-ai/agents that referenced this pull request Oct 1, 2026
fullsend-ai/fullsend#7243 was split into #7914 (status sanitiser),
#7915 (readonly_repo), #7916 (comment-author check) and #7917
(--only-if-exists, stacked on #7916). Regenerating from all four merged
onto fullsend main gives output byte-identical to #7243's last head,
so only the reference changes.

Assisted-by: Claude (fix), Claude (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
jctanner pushed a commit to jctanner/fullsend that referenced this pull request Oct 1, 2026
…y is unresolved

sticky.Post resolved the poster's login with GetAuthenticatedUser and,
on failure, logged a warning and continued with an empty login.
FindMarkedComment skipped its author filter whenever botUser was
empty, so it matched the first comment carrying the marker regardless
of who wrote it, letting an unverified (and potentially spoofed)
comment get edited in-place. fullsend-ai#7243/fullsend-ai#7916 already closed this gap for
postTrackerStickyComment; this brings sticky.Post (used by
post-comment / post-review) to the same rule.

Changes in internal/sticky/sticky.go:
* Added resolveBotUser, which retries a failed or empty
  GetAuthenticatedUser lookup per a package-level
  botUserLookupBackoff (tests shorten it), honouring ctx
  cancellation. This mirrors resolveTrackerSelf in
  internal/cli/issues.go, but is kept local to this package since
  internal/cli imports sticky and sharing it would create an import
  cycle.
* Post now calls resolveBotUser before listing or writing anything.
  If the login can't be resolved, it returns an error naming the
  cause (wrapping the lookup error) and calls neither
  UpdateIssueComment nor CreateIssueComment, including on dry run,
  so dry-run output never implies an edit would occur when none
  safely could.
* FindMarkedComment now treats an empty botUser as "match nothing"
  instead of "match any author," and requires an exact author match
  plus the marker.

Tests in internal/sticky/sticky_test.go:
* Inverted TestFindMarkedComment_EmptyBotUser to assert nil instead
  of matching any author.
* Added coverage for a persistent identity-lookup failure (errors,
  posts/edits nothing), a transient failure that succeeds on retry
  (edits the bot's own comment), a planted marker comment alongside
  the bot's own comment (only the bot's comment is edited), a lone
  planted comment (a new comment is created and the planted one is
  untouched), and dry run with an unresolved identity (errors,
  doesn't claim an edit would happen).
* flakyAuthClient wraps forge.Client to fail GetAuthenticatedUser a
  configurable number of times before succeeding, since FakeClient's
  Errors map isn't a call-sequence.
* Set AuthenticatedUser on the fake client in TestPost_DryRun and
  TestPost_DryRunExisting, which previously relied on the degraded
  fallback (an empty login) succeeding.

Scope: internal/cli/issues.go (and its resolveTrackerSelf /
postTrackerStickyComment) is untouched; that package already has its
own equivalent, added by fullsend-ai#7916, and the issue asks to keep the retry
local to internal/sticky.

Closes fullsend-ai#7884
@waynesun09
waynesun09 deleted the agent-6966-post-status-sanitize branch October 1, 2026 22:54

This branch was successfully deployed

1 active and 1 inactive deployments
site-preview — 069e2845 Deployed Oct 1, 2026 by github-actions[bot]
dev — 069e2845 Deployed Oct 1, 2026 by waynesun09 via behaviour #14740
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fullsend-no-fix Skip bot-triggered fix agent runs risk/moderate PR risk: moderate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

issues post-comment: verify the comment author before editing, and fail closed on an unresolvable identity

2 participants