Skip to content

feat(#3697): add on_failure mode for comment.completion status notifications - #5736

Open
ralphbean wants to merge 17 commits into
mainfrom
feat/3697-on-failure-comment-completion
Open

feat(#3697): add on_failure mode for comment.completion status notifications#5736
ralphbean wants to merge 17 commits into
mainfrom
feat/3697-on-failure-comment-completion

Conversation

@ralphbean

Copy link
Copy Markdown
Member

Summary

  • Add on_failure as a valid value for status_notifications.comment.completion
  • When set, completion comments are posted only on failure/cancellation — suppressed on success
  • On success with on_failure, the start comment is silently cleaned up (deleted)
  • Config validation rejects on_failure for comment.start (no outcome to evaluate yet)

Phase 1 of #3697 — comment-only changes. Reaction support is a follow-up.

Test plan

  • Unit tests: config validation accepts on_failure for completion, rejects for start
  • Unit tests: PostCompletion with on_failure suppresses on success, fires on failure/cancelled
  • Unit tests: cleanup of start comment when completion suppressed
  • All existing tests pass (no regressions)
  • Functional test: fullsend run triage with on_failure config on a test issue

🤖 Generated with Claude Code

@ralphbean ralphbean added the fullsend-fix Enables automatic bot-triggered fix runs on human-authored PRs label Jul 29, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 29, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 5:44 PM UTC · Ended 5:46 PM UTC
Commit: 1c8b95b · View workflow run →

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

Site preview

Preview: https://a589e5f5-site.fullsend-ai.workers.dev

Commit: d16ed236e1e2660a913f71b8be9813cb680c7277

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 29, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:47 PM UTC · Completed 6:01 PM UTC
Commit: 2829847 · View workflow run →

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

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

Files with missing lines Patch % Lines
internal/cli/reconcilestatus.go 93.10% 2 Missing ⚠️
internal/statuscomment/statuscomment.go 86.66% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@ralphbean
ralphbean marked this pull request as ready for review July 29, 2026 18:01
@ralphbean
ralphbean requested a review from a team as a code owner July 29, 2026 18:01
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [logic-error] action.yml:432 — The reconcile step passes ${{ job.status }} as JOB_STATUS, but job.status reflects the cumulative outcome of ALL prior steps in the job. This PR moves the Upload fullsend artifacts step before the reconcile step (both use if: always()), so if the upload fails after a successful fullsend run, job.status will be "failure" and ReconcileOrphaned will synthesize a spurious "Interrupted" comment — falsely claiming the agent was terminated when it completed normally. The Run fullsend step has id: run, so steps.run.outcome is available and would accurately reflect only the fullsend run's result.
    Remediation: Pass ${{ steps.run.outcome }} instead of ${{ job.status }} for the JOB_STATUS env var.

Low

  • [edge-case] internal/statuscomment/statuscomment.go:145PostStart suppresses the start comment when completion is on_failure by checking n.cfg.Comment.Completion != "on_failure". A user who sets start: enabled and completion: on_failure will not see a start comment, which may be surprising. The behavior is deliberate and documented: posting then deleting a start comment on success still triggers a GitHub notification pointing to a deleted comment, which defeats the noise-reduction purpose.

  • [edge-case] internal/statuscomment/statuscomment.go:521 — The synthesis condition completionMode == "on_failure" && (wasSkipped || (jobStatus != "" && jobStatus != "success")) accepts any non-empty, non-"success" jobStatus value. Any unexpected value (e.g., a typo in the --job-status flag) would trigger synthesis. The risk is low since the flag value is controlled by action.yml, not user input.

  • [api-shape] internal/statuscomment/statuscomment.go:484ReconcileOrphaned now has 13 positional parameters (up from 9), adding completionMode, jobStatus, wasSkipped, and agentDescription. The codebase uses options struct patterns elsewhere (e.g., config.LoadOpts). Pre-existing concern worsened by four parameters.

  • [missing-doc] docs/guides/dev/cli-internals.md:122 — The --was-skipped flag was added to the reconcile-status command but was not added to the CLI tree documentation. The PR added --fullsend-dir and --job-status to the CLI tree but omitted --was-skipped.


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

Medium

  • [logic-error] action.yml:431 — The reconcile step passes ${{ job.status }} as JOB_STATUS, but job.status reflects the cumulative outcome of ALL prior steps in the job. This PR moves the Upload fullsend artifacts step before the reconcile step (both use if: always()), so if the upload fails after a successful fullsend run, job.status will be "failure" and ReconcileOrphaned will synthesize a spurious "Interrupted" comment — falsely claiming the agent was terminated when it completed normally. The Run fullsend step has id: run, so steps.run.outcome is available and would accurately reflect only the fullsend run's result.
    Remediation: Pass ${{ steps.run.outcome }} instead of ${{ job.status }} for the JOB_STATUS env var.

Low

  • [edge-case] internal/statuscomment/statuscomment.go:146PostStart suppresses the start comment when completion is on_failure by checking n.cfg.Comment.Completion != "on_failure". A user who sets start: enabled and completion: on_failure will not see a start comment, which may be surprising. The behavior is deliberate and documented: posting then deleting a start comment on success still triggers a GitHub notification pointing to a deleted comment, which defeats the noise-reduction purpose.

  • [edge-case] internal/statuscomment/statuscomment.go:514 — The synthesis condition completionMode == "on_failure" && jobStatus != "" && jobStatus != "success" accepts any non-empty, non-"success" jobStatus value. Any unexpected value (e.g., a typo in the --job-status flag) would trigger synthesis. The risk is low since the flag value is controlled by action.yml, not user input.

  • [diagnostic-message-prefix] internal/cli/reconcilestatus.go:102 — The diff introduces INFO: prefix in diagnostic messages (lines 102, 104). This prefix does not appear anywhere else in internal/cli/*.go files. The codebase uses WARNING: extensively (20+ occurrences) for stderr prefixes; non-warning diagnostics use no prefix.

  • [api-shape] internal/statuscomment/statuscomment.go:478ReconcileOrphaned now has 11 positional parameters (up from 9), adding completionMode and jobStatus. The codebase uses options struct patterns elsewhere (e.g., config.LoadOpts). Pre-existing concern worsened by two parameters.


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

  • [logic-error] action.yml:431 — The reconcile step passes ${{ job.status }} as JOB_STATUS, but job.status reflects the cumulative outcome of ALL prior steps in the job. This PR moves the Upload fullsend artifacts step before the reconcile step (both use if: always()), so if the upload fails after a successful fullsend run, job.status will be "failure" and ReconcileOrphaned will synthesize a spurious "Interrupted" comment — falsely claiming the agent was terminated when it completed normally. The Run fullsend step has id: run, so steps.run.outcome is available and would accurately reflect only the fullsend run's result.
    Remediation: Pass ${{ steps.run.outcome }} instead of ${{ job.status }} for the JOB_STATUS env var.

Low

  • [edge-case] internal/statuscomment/statuscomment.go:146PostStart suppresses the start comment when completion is on_failure by checking n.cfg.Comment.Completion != "on_failure". A user who sets start: enabled and completion: on_failure will not see a start comment, which may be surprising. The behavior is deliberate and documented: posting then deleting a start comment on success still triggers a GitHub notification pointing to a deleted comment, which defeats the noise-reduction purpose.

  • [edge-case] internal/statuscomment/statuscomment.go:514 — The synthesis condition completionMode == "on_failure" && jobStatus != "" && jobStatus != "success" accepts any non-empty, non-"success" jobStatus value. Any unexpected value (e.g., a typo in the --job-status flag) would trigger synthesis. The risk is low since the flag value is controlled by action.yml, not user input.

  • [diagnostic-message-prefix] internal/cli/reconcilestatus.go:106 — The diff introduces INFO: prefix in diagnostic messages (lines 106, 108). This prefix does not appear anywhere else in internal/cli/*.go files. The codebase uses WARNING: extensively (20+ occurrences) for stderr prefixes; non-warning diagnostics use no prefix.

  • [api-shape] internal/statuscomment/statuscomment.go:478ReconcileOrphaned now has 11 positional parameters (up from 9), adding completionMode and jobStatus. The codebase uses options struct patterns elsewhere (e.g., config.LoadOpts). Pre-existing concern worsened by two parameters.


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)

Review

Findings

Medium

  • [logic-error] action.yml — The reconcile step uses job.status (via JOB_STATUS) to decide whether to synthesize an "Interrupted" comment when completionMode == "on_failure". However, job.status reflects the outcome of ALL previous steps in the job, not just the fullsend run step. If the fullsend run succeeds (agent completes normally, PostCompletion suppresses the comment as designed) but the Upload fullsend artifacts step fails, job.status will be "failure" and ReconcileOrphaned will create a spurious "Interrupted" comment — falsely claiming the agent was terminated when it actually completed normally.
    Remediation: Pass the fullsend run step's outcome (e.g., steps.<run-step-id>.outcome) instead of job.status for the --job-status flag, or have PostCompletionWithDetail write a sentinel file when it suppresses the comment so ReconcileOrphaned can distinguish "completed normally" from "hard-killed."

Low

  • [edge-case] internal/statuscomment/statuscomment.go:146PostStart suppresses the start comment when completion is on_failure by checking n.cfg.Comment.Completion != "on_failure". A user who sets start: enabled and completion: on_failure will not see a start comment, which may be surprising. The behavior is deliberate and documented: posting then deleting a start comment on success still triggers a GitHub notification pointing to a deleted comment, which defeats the noise-reduction purpose. See also: [scope-creep] finding at this location.

  • [scope-creep] internal/statuscomment/statuscomment.go:146 — The implementation couples start and completion settings: completion=on_failure auto-suppresses start comments. A user cannot configure start:enabled + completion:on_failure. This is a deliberate tradeoff to avoid sending GitHub notifications for comments that would be deleted on success. See also: [edge-case] finding at this location.

  • [undisclosed-scope] internal/cli/reconcilestatus.go:95 — The PR adds reconciliation infrastructure for on_failure mode (config loading, --fullsend-dir and --job-status flags, workflow integration, completion-mode-aware synthesis logic) that is not mentioned in the PR description's claimed scope ("Phase 1 of Triage agent causes unnecessary notifications - should skip initial comment #3697 — comment-only changes").

  • [diagnostic-message-prefix] internal/cli/reconcilestatus.go — The diff introduces INFO: prefix in diagnostic messages. This prefix does not appear anywhere else in internal/cli/*.go files. The codebase uses WARNING: extensively (20+ occurrences) but never INFO:.

  • [api-shape] internal/statuscomment/statuscomment.go:480ReconcileOrphaned now has 11 positional parameters (up from 9), adding completionMode and jobStatus. The codebase uses options struct patterns elsewhere (e.g., config.LoadOpts). Pre-existing concern worsened by two parameters.


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 (4)

Review

Findings

Low

  • [edge-case] internal/statuscomment/statuscomment.go:148 — PostStart suppresses the start comment when completion is on_failure by checking n.cfg.Comment.Completion != "on_failure". A user who sets start: enabled and completion: on_failure will not see a start comment, which may be surprising. The behavior is deliberate and documented: posting then deleting a start comment on success still triggers a GitHub notification pointing to a deleted comment, which defeats the noise-reduction purpose.

  • [api-shape] internal/statuscomment/statuscomment.go:480ReconcileOrphaned now has 11 positional parameters (up from 9), adding completionMode and jobStatus. The codebase uses options struct patterns elsewhere (e.g., config.LoadOpts). Pre-existing concern worsened by two parameters.

  • [config-loading-pattern] internal/cli/reconcilestatus.go:94 — Config loading uses a nested switch-case pattern that differs from simpler patterns elsewhere but is warranted for defensive error handling since config loading is optional and errors should not abort the reconcile operation.

Previous run (5)

Review

Findings

Low

  • [implicit-coupling] internal/statuscomment/statuscomment.go:148 — Setting completion: on_failure silently overrides start: enabled. PostStart checks n.cfg.Comment.Completion != "on_failure" and suppresses start comments regardless of the start setting. The coupling is documented in user-facing docs (customizing-agents.md completion modes table and prose), the code comment on PostStart, and is a deliberate design choice — posting a start comment that gets deleted on success would still trigger a GitHub notification pointing to a deleted comment. See also: [scope-interpretation] finding at this location.

  • [scope-interpretation] internal/statuscomment/statuscomment.go:146 — Issue Triage agent causes unnecessary notifications - should skip initial comment #3697 requested skipping the start comment to reduce notification noise. The PR implements a broader feature (on_failure completion mode) that solves the notification problem by suppressing both start and completion comments on success. The implementation scope is wider than the simplest interpretation of the issue, but achieves the same UX outcome and provides value for other agent types beyond triage. See also: [implicit-coupling] finding at this location.

  • [scope-creep] internal/statuscomment/statuscomment.go:516 — Orphan synthesis logic extends beyond the simplest interpretation of issue Triage agent causes unnecessary notifications - should skip initial comment #3697. However, synthesis is a necessary correctness measure for on_failure mode — without it, a hard-killed agent in on_failure mode would be completely invisible (no start comment was posted, process died before PostCompletion). The logic is conservative: triggers only when completionMode == "on_failure" && jobStatus != "" && jobStatus != "success". See also: [edge-case] finding at this location.

  • [edge-case] internal/statuscomment/statuscomment.go:516 — In ReconcileOrphaned, synthesized interrupted comments use the reason parameter (terminated vs. cancelled) for the status label, not jobStatus. A job that GitHub reports as "failure" will get a "Terminated" label if the process was SIGKILL'd. This is semantically correct for the orphaned-process scenario (the label describes how the process died, not the job outcome). See also: [scope-creep] finding at this location.

  • [platform-coupling] internal/statuscomment/statuscomment.go:467 — The jobStatus parameter docstring references GitHub Actions job status values. While the string comparisons themselves are generic CI concepts (success/failure/cancelled), the docstring creates a documentation-level coupling to GHA semantics. Currently moot since the platform is exclusively github-actions (config validation enforces this).

  • [api-shape] internal/statuscomment/statuscomment.go:480ReconcileOrphaned now has 11 positional parameters (up from 9), adding completionMode and jobStatus. The codebase uses options struct patterns elsewhere (e.g., config.LoadOpts). Pre-existing concern worsened by two parameters.

  • [naming-consistency] internal/statuscomment/statuscomment.go:130 — New shouldPostCompletion helper uses a different naming convention (should*) than the existing commentEnabled helper. The difference reflects a genuine semantic distinction — commentEnabled is a simple config check while shouldPostCompletion encodes richer logic depending on both config and runtime status.

  • [runtime-dependency] internal/cli/reconcilestatus.go:94 — The reconcile-status command loads config at runtime to determine completion mode. This is well-defended: loading is skipped when --fullsend-dir is not passed, MissingOK: true handles absent configs, errors print a WARNING and fall through to default (enabled) behavior, and the nil check on the writer handles the MissingOK path.

  • [config-loading-verbosity] internal/cli/reconcilestatus.go:693 — Config loading code uses a nested type assertion pattern (13 lines). Functionally correct with clear, linear logic: check flag, load config, handle error, type-assert, read value. Single call site — extracting a helper would add indirection without meaningful reuse.

  • [naming-clarity] internal/config/config.go:116 — The on_failure value describes when to post completion comments but also controls start comment behavior. The name doesn't hint at the broader suppression effect on start comments, though this is documented in user-facing docs.

  • [validation-pattern] internal/config/config.go:454 — Config validation now splits into validStartValues and validCompletionValues where previously a single validCommentValues was reused. The split is a correctness requirement since start and completion now accept different value sets.

  • [config-scope-restriction] docs/guides/user/customizing-agents.md:496status_notifications is org-wide only, not per-repo. This is a pre-existing design constraint (not introduced by this PR) and is reasonable from a UX consistency perspective.

Previous run (6)

Review

Findings

Low

  • [implicit-coupling] internal/statuscomment/statuscomment.go:146 — Setting completion: on_failure silently overrides start: enabled. PostStart checks n.cfg.Comment.Completion != "on_failure" and suppresses start comments regardless of the start setting. The coupling is documented in user-facing docs (customizing-agents.md completion modes table and prose), the code comment on PostStart, and is a deliberate design choice — posting a start comment that gets deleted on success would still trigger a GitHub notification pointing to a deleted comment.

  • [scope-creep] internal/statuscomment/statuscomment.go:514 — Orphan synthesis logic (lines 507–520) extends beyond the simplest interpretation of issue Triage agent causes unnecessary notifications - should skip initial comment #3697, which asked to skip posting the initial comment. However, synthesis is a necessary correctness measure for on_failure mode — without it, a hard-killed agent in on_failure mode would be completely invisible (no start comment was posted, process died before PostCompletion). The logic is conservative: triggers only when completionMode == "on_failure" && jobStatus != "" && jobStatus != "success".

  • [edge-case] internal/statuscomment/statuscomment.go:514 — In ReconcileOrphaned, synthesized interrupted comments use the reason parameter (terminated vs. cancelled) for the status label, not jobStatus. A job that GitHub reports as "failure" will get a "Terminated" label if the process was SIGKILL'd. This is semantically correct for the orphaned-process scenario (the label describes how the process died, not the job outcome) but may confuse operators. See also: [scope-creep] finding at this location.

  • [platform-coupling] internal/statuscomment/statuscomment.go:467 — The jobStatus parameter docstring references GitHub Actions job status values. While the string comparisons themselves are generic CI concepts (success/failure/cancelled), the docstring creates a documentation-level coupling to GHA semantics. Currently moot since the platform is exclusively github-actions (config validation enforces this).

  • [runtime-dependency] internal/cli/reconcilestatus.go:94 — The reconcile-status command loads config at runtime to determine completion mode. This is well-defended: loading is skipped when --fullsend-dir is not passed, MissingOK: true handles absent configs, errors print a WARNING and fall through to default (enabled) behavior, and the nil check on the writer handles the MissingOK path.

  • [api-shape] internal/statuscomment/statuscomment.go:478ReconcileOrphaned now has 11 positional parameters (up from 9), adding completionMode and jobStatus. The codebase uses options struct patterns elsewhere (e.g., config.LoadOpts). Pre-existing concern worsened by two parameters.

  • [naming-clarity] internal/config/config.go:116 — The on_failure value describes when to post completion comments but also controls start comment behavior. The name doesn't hint at the broader suppression effect on start comments, though this is documented in user-facing docs.

Previous run (7)

Review

Findings

Medium

  • [implicit-coupling] internal/statuscomment/statuscomment.go:146 — Setting completion: on_failure silently overrides start: enabled. PostStart adds a hard-coded check n.cfg.Comment.Completion != "on_failure" that suppresses start comments regardless of the start setting. A user who sets start: enabled, completion: on_failure expecting start comments would not get them. The coupling is documented in the user-facing docs and the code comment explains the rationale (posting a start comment that gets deleted on success would trigger a notification pointing to a deleted comment), but the design still allows one config field to silently override another's explicit value.

Low

  • [missing-test] internal/statuscomment/statuscomment.go:128shouldPostCompletion includes "timeout" as a posting trigger for on_failure mode, but there is no test exercising PostCompletion with status="timeout" under on_failure mode. Tests cover success, failure, cancelled, and skipped but not timeout.

  • [edge-case] internal/statuscomment/statuscomment.go — In ReconcileOrphaned, when synthesizing an interrupted comment for on_failure mode with a non-success job status, the termination reason defaults to "terminated", so the synthesized comment reads "Terminated" even when jobStatus is "failure". This is correct for the orphaned-process scenario but may confuse operators.

  • [api-shape] internal/statuscomment/statuscomment.goReconcileOrphaned now has 11 positional parameters (up from 9), adding completionMode and jobStatus. The codebase uses options struct patterns elsewhere (e.g., config.LoadOpts). Pre-existing concern worsened by two parameters.

  • [naming-clarity] internal/config/config.go — The on_failure value describes when to post completion comments but also controls start comment behavior. The name doesn't hint at the broader suppression effect on start comments.

  • [naming-conventions] internal/config/config.go — Renaming validCommentValues to validStartValues and introducing validCompletionValues is reasonable given the now-different valid sets, but creates two arrays where the Start values are identical to the original shared array.

Previous run (8)

Review

Findings

Medium

  • [implicit-coupling] internal/statuscomment/statuscomment.go:146 — Setting completion: on_failure silently overrides start: enabled. PostStart adds a hard-coded check n.cfg.Comment.Completion != "on_failure" that suppresses start comments regardless of the start setting. A user who sets start: enabled, completion: on_failure expecting start comments would not get them. The coupling is documented in the user-facing docs and the code comment explains the rationale (posting a start comment that gets deleted on success would trigger a notification pointing to a deleted comment), but the design still allows one config field to silently override another's explicit value.

Low

  • [api-shape] internal/statuscomment/statuscomment.go:468ReconcileOrphaned now has 11 positional parameters (up from 9), adding completionMode and jobStatus. The codebase uses options struct patterns elsewhere (e.g., config.LoadOpts). Pre-existing concern worsened by two parameters.

  • [error-handling] internal/cli/reconcilestatus.go:96 — Config loading warning omits fallback behavior context. When loading fails, completionMode defaults to empty string (which means enabled behavior). Adding "using default completion mode" to the warning would improve operator clarity.

  • [naming-clarity] internal/config/config.go — The on_failure value describes when to post completion comments but also controls start comment behavior. The name doesn't hint at the broader suppression effect on start comments.

  • [naming-conventions] internal/config/config.go:454 — Renaming validCommentValues to validStartValues and introducing validCompletionValues is reasonable given the now-different valid sets, but creates two arrays where the Start values are identical to the original shared array.

Previous run (9)

Review

Findings

Medium

  • [edge-case] internal/statuscomment/statuscomment.go:504ReconcileOrphaned synthesizes an interrupted comment when completionMode == "on_failure" && jobStatus != "success", but there is no guard against jobStatus being an empty string. The --job-status flag is optional (not marked required), so omitting it produces jobStatus == "", which satisfies != "success" and triggers a spurious synthesized comment even though the job outcome is unknown. In the current action.yml, --job-status is always passed, so this path is not triggered in practice today — but the CLI contract allows omission.
    Remediation: Add an empty-string check: if completionMode == "on_failure" && jobStatus != "" && jobStatus != "success". Alternatively, make --job-status required when --fullsend-dir is provided.

Low

  • [api-shape] internal/statuscomment/statuscomment.go:478ReconcileOrphaned now has 11 positional parameters (up from 9), adding completionMode and jobStatus. The codebase uses options struct patterns elsewhere (e.g., config.LoadOpts). Pre-existing concern worsened by two parameters.
Previous run (10)

Review

Findings

Medium

  • [error-handling-idiom] internal/cli/reconcilestatus.go:91 — Config loading warning uses lowercase warning: prefix. The codebase convention in internal/cli uses uppercase WARNING: consistently across run.go, bootstrap_scan.go, mint.go, and repos.go (20+ instances). The lowercase prefix is inconsistent with the established pattern.
    Remediation: Change to uppercase: fmt.Fprintf(os.Stderr, "WARNING: could not load config from %s: %v\n", fullsendDir, err)

Low

  • [scope-creep] internal/statuscomment/statuscomment.go:511 — Auto-suppression of start comment when completion is on_failure adds an implicit coupling between start and completion settings. When comment.completion is "on_failure", PostStart skips the start comment regardless of the start setting. This is a deliberate design choice by the feature author (who also authored the "independent and composable" requirement on Triage agent causes unnecessary notifications - should skip initial comment #3697), but documenting the coupling in the config reference would improve discoverability.

  • [api-shape] internal/statuscomment/statuscomment.go:460ReconcileOrphaned's parameter list grew from 9 to 11 positional parameters (adding completionMode and jobStatus). The codebase uses options struct patterns for similar functions. Pre-existing concern incremented by two.

  • [naming-consistency] internal/cli/reconcilestatus.go:90 — The local variable completionMode uses a Mode suffix not present in the config field Comment.Completion. The term "mode" is not used elsewhere for this concept.

Previous run (11)

Review

Findings

Critical

  • [logic-error] internal/statuscomment/statuscomment.go:498ReconcileOrphaned synthesizes a false "Interrupted" comment on every successful run when completionMode is "on_failure". The flow: (1) PostStart is suppressed because completion == "on_failure", so no start comment marker exists. (2) The agent completes successfully; PostCompletionWithDetail suppresses the completion comment (shouldPostCompletion returns false for "success"). (3) The post-job reconcile step runs unconditionally (if: always()), reads the config, passes completionMode="on_failure", finds no marker comment, and creates a synthesized "Interrupted" comment. This directly undermines the noise-reduction intent — every successful on_failure run leaves a false "Terminated" comment on the issue/PR.
    Remediation: ReconcileOrphaned needs a way to distinguish "completed successfully, no comment needed" from "hard-killed before PostCompletion ran." Simplest option: have the action.yml post step check JOB_STATUS and skip the synthesis when the job succeeded (the shell already has $JOB_STATUS from job.status). Alternatively, have PostCompletionWithDetail write a lightweight sentinel (e.g., a terminal-tagged hidden comment) when it suppresses a completion comment.

Medium

  • [error-handling-idiom] internal/cli/reconcilestatus.go:91 — Config loading errors are silently swallowed (if writer, err := ...; err == nil). A config parse error causes completionMode to default to empty string, silently disabling the on_failure synthesis path. This differs from the repo's error-handling idiom where non-fatal issues are at minimum warned about.
    Remediation: Log a warning to stderr when config loading fails (e.g., fmt.Fprintf(os.Stderr, "warning: could not load config from %s: %v\n", fullsendDir, err)).

  • [missing-cli-flag] docs/guides/dev/cli-internals.md:151 — The CLI tree for reconcile-status lists all flags but is missing the new --fullsend-dir flag added in this PR.
    Remediation: Add --fullsend-dir to the reconcile-status flag list.

Low

  • [edge-case] internal/statuscomment/statuscomment.go:131shouldPostCompletion includes "timeout" in the on_failure allowlist, but the user-facing documentation states completion is posted "only when the agent fails or is cancelled." Including timeout is defensible (it is failure-adjacent), but the documentation should mention it for consistency.

  • [code-organization] internal/statuscomment/statuscomment.go:136PostStart now couples start notification logic to completion configuration via n.cfg.Comment.Completion != "on_failure". The doc comment explains the rationale and the coupling is intentional. Noting for context.

  • [api-shape] internal/statuscomment/statuscomment.go:460ReconcileOrphaned's parameter list grew to 10 positional parameters. The codebase uses options struct patterns for similar functions (e.g., config.LoadOpts, mintclient.MintRequest). Pre-existing concern incremented by one.

  • [naming-consistency] internal/statuscomment/statuscomment.go:460 — The new parameter is named completionMode while the config field is Comment.Completion. The term "mode" is not used elsewhere for this concept.


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 (12)

Review

Findings

Medium

  • [logic-error] internal/statuscomment/statuscomment.go:131shouldPostCompletion treats "skipped" as a non-success status, so on_failure mode will post a completion comment for skipped runs. The PR body says completion comments are posted "only on failure/cancellation — suppressed on success," and the user-facing documentation states on_failure will "post only when the agent fails or is cancelled." However, the code uses status != "success" which is a broader predicate that also matches "skipped." A skipped run is a benign pre-script outcome (e.g., the pre-script decided no work is needed), and posting a completion comment for it contradicts the noise-reduction intent. No test covers the on_failure + skipped combination.
    Remediation: Confirm design intent — should on_failure suppress skipped as well? If so, change the predicate to return status == "failure" || status == "cancelled" (allowlist of failure outcomes rather than blocklist of success). Add a test for on_failure + skipped to codify the decision either way.

Low

  • [code-organization] internal/statuscomment/statuscomment.goPostStart now couples start notification logic to completion configuration via n.cfg.Comment.Completion != "on_failure". The doc comment explains the rationale (posting a start comment that gets deleted on success would still trigger a GitHub notification pointing to a deleted comment), and the coupling is intentional. No action needed — noting for context.
Previous run (13)

Review

Findings

Medium

  • [logic-error] internal/statuscomment/statuscomment.go:131shouldPostCompletion uses status != "success" as the on_failure predicate, but the documentation states on_failure posts "only when the agent fails or is cancelled." The "skipped" status (set in internal/cli/run.go:680 when a pre-script decides no work is needed) would trigger a completion comment under on_failure mode. A skipped run is a benign outcome — posting a completion comment for it contradicts the noise-reduction intent. No test covers the on_failure + skipped combination.

Low

  • [scope-authorization-mismatch] internal/statuscomment/statuscomment.go:138 — Issue Triage agent causes unnecessary notifications - should skip initial comment #3697 requests skipping start comments to reduce notifications. This PR implements on_failure mode for completion comments as Phase 1, coupling start comment suppression to completion mode. The implementation is internally consistent and the phased approach is explicitly described, but the issue author (deboer-tim) differs from the PR author (ralphbean); confirmation that this approach meets the requirement would validate scope alignment.
Previous run (14)

Review

Findings

Low

  • [scope-authorization-mismatch] internal/config/config.go:115 — The PR implements on_failure mode for completion comments as Phase 1 of Triage agent causes unnecessary notifications - should skip initial comment #3697, rather than the start-comment-skip described in the issue. The PR author and issue author are the same person, and the PR description explicitly frames this as a phased approach. The implementation is internally consistent and correct; the question is purely about project planning priorities.
Previous run (15)

Review

Findings

Low

  • [technical documentation accuracy] internal/config/config.go:115 — The doc comment on CommentNotificationConfig states "Valid values: "enabled" (default when parent is set), "disabled"" but the Completion field now also accepts "on_failure". The doc comment should reflect per-field valid values.

  • [stale-documentation] docs/guides/getting-started/operations.md:159 — The status_notifications example in operations.md only lists "enabled" and "disabled" for completion, missing the new "on_failure" value. Consider updating the example or redirecting to the comprehensive docs in customizing-agents.md (matching the pattern used in the running-agents-locally.md update).

  • [documentation-style] internal/statuscomment/statuscomment.go:126 — The shouldPostCompletion function has a doc comment while the similar helper commentEnabled does not. Minor style inconsistency in doc comment coverage for unexported boolean helpers.


Labels: PR adds a new user-facing config option (on_failure mode) with documentation updates

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added ready-for-merge All reviewers approved — ready to merge type/feature New capability request component/docs User-facing documentation labels Jul 29, 2026
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add on_failure mode for completion status comments

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Add on_failure option to completion status notifications to reduce success noise.
• Suppress completion comments on success while cleaning up the start comment.
• Extend config validation and add unit tests for new completion behavior.
Diagram

graph TD
  A[/"config.yaml"/] --> B["Config validation"] --> C["Notifier.PostCompletion"] --> D{"Completion mode?"}
  D -->|"enabled / on_failure+non-success"| E["Forge client"] --> F["Issue/PR comment"]
  D -->|"disabled / on_failure+success"| G["Delete start comment"] --> E
  subgraph Legend
    direction LR
    _io[/"Config input"/] ~~~ _svc["Component"] ~~~ _dec{"Decision"}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Introduce structured enum/typed mode instead of string literals
  • ➕ Avoids scattering string values like "on_failure"/"enabled" across code
  • ➕ Makes future expansion (e.g., on_success, on_cancelled) safer and more discoverable
  • ➖ Requires broader refactor across config parsing/serialization and existing APIs
  • ➖ Higher migration/compatibility surface for a small behavioral addition
2. Generalize to a single notification policy function for start+completion
  • ➕ Centralizes all status-notification logic (start/completion) in one place
  • ➕ Easier to enforce consistent semantics across notification types
  • ➖ More refactoring than needed for a phase-1 feature
  • ➖ Risk of behavior changes to existing start-comment logic

Recommendation: Current approach is appropriate for a targeted feature: extend completion-only validation, add a small predicate helper (shouldPostCompletion), and keep behavior localized to PostCompletion with strong unit test coverage. If notification modes expand further (e.g., reaction support or more outcome-based rules), consider migrating to a typed enum/policy layer to reduce stringly-typed branching.

Files changed (6) +201 / -8

Enhancement (2) +20 / -7
config.goAccept on_failure for comment.completion and reject it for comment.start +5/-4

Accept on_failure for comment.completion and reject it for comment.start

• Splits validation into start vs completion allowed values. Extends completion validation to allow 'on_failure' while keeping start restricted to enabled/disabled.

internal/config/config.go

statuscomment.goGate completion comment posting with on_failure-aware predicate +15/-3

Gate completion comment posting with on_failure-aware predicate

• Introduces 'shouldPostCompletion(val, status)' to interpret 'on_failure' as "post unless success". Updates 'PostCompletion' to suppress completion comments when appropriate and clean up the start comment to avoid leaving an orphaned "Started" comment.

internal/statuscomment/statuscomment.go

Tests (2) +156 / -0
config_test.goAdd config validation/parsing tests for on_failure completion +55/-0

Add config validation/parsing tests for on_failure completion

• Adds unit tests verifying 'on_failure' is accepted for 'status_notifications.comment.completion' and rejected for '.start'. Includes a YAML parse test to ensure the value round-trips through config parsing.

internal/config/config_test.go

statuscomment_test.goAdd notifier tests covering on_failure success/failure/cancelled outcomes +101/-0

Add notifier tests covering on_failure success/failure/cancelled outcomes

• Adds unit tests ensuring 'on_failure' suppresses completion on success (and deletes the start comment), while still posting completion on failure and cancellation. Also covers the case where start comments are disabled and completion is on_failure.

internal/statuscomment/statuscomment_test.go

Documentation (2) +25 / -1
customizing-agents.mdDocument Status Notifications and add on_failure completion mode +24/-0

Document Status Notifications and add on_failure completion mode

• Adds a new user-facing "Status Notifications" section describing 'status_notifications.comment.start' and '.completion'. Documents 'on_failure' behavior (post only on failure/cancellation; delete start comment on success) alongside enabled/disabled modes.

docs/guides/user/customizing-agents.md

running-agents-locally.mdUpdate local-running guide to link to new Status Notifications docs +1/-1

Update local-running guide to link to new Status Notifications docs

• Repoints the status notification reference from the operations guide to the new section in 'customizing-agents.md'. Keeps guidance user-focused where configuration is described.

docs/guides/user/running-agents-locally.md

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 29, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 6:03 PM UTC · Completed 6:04 PM UTC
Commit: 2829847 · View workflow run →

@qodo-code-review

qodo-code-review Bot commented Jul 29, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Remediation recommended

1. Docs imply per-repo support ✓ Resolved 🐞 Bug ≡ Correctness
Description
Docs state status_notifications in config.yaml controls status comments, but per-repo configs do
not have a status_notifications field and YAML unmarshalling ignores unknown fields, so
configuring it in per-repo mode will have no effect.
Code

docs/guides/user/customizing-agents.md[R499-507]

+Agent workflows post status comments on issues and PRs when they start and complete. Control this with `status_notifications` in `config.yaml`:
+
+```yaml
+defaults:
+  status_notifications:
+    comment:
+      start: enabled      # "enabled" (default) | "disabled"
+      completion: enabled  # "enabled" (default) | "on_failure" | "disabled"
+```
Relevance

●●● Strong

Team often fixes doc/behavior mismatches to avoid misleading operators, especially per-repo vs
org-mode details.

PR-#5454

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The docs claim the setting applies to config.yaml generally, but the code only defines and reads
it for org-mode; per-repo parsing uses yaml.Unmarshal into a struct without that field so the key
is ignored.

docs/guides/user/customizing-agents.md[497-520]
internal/config/config.go[108-129]
internal/config/config.go[493-573]
internal/cli/run.go[2893-2904]

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 docs introduce `status_notifications` without clarifying that it is only supported in org-mode configuration. In per-repo mode, the field is not part of the schema/struct and will be silently ignored, so users can apply the documented config and see no change.

### Issue Context
- Org-mode carries `defaults.status_notifications`.
- Per-repo config struct has no `StatusNotifications` field.
- YAML parsing uses `yaml.Unmarshal` (non-strict), so unknown keys are ignored.
- The CLI only reads `StatusNotifications()` when the loaded config implements `OrgConfigReader`.

### Fix Focus Areas
- docs/guides/user/customizing-agents.md[497-520]
- internal/config/config.go[108-129]
- internal/config/config.go[493-573]
- internal/cli/run.go[2893-2904]

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



Informational

2. Misleading cleanup warning ✓ Resolved 🐞 Bug ◔ Observability
Description
PostCompletion now suppresses completion comments for on_failure+success, but cleanup warnings
still claim completion was “disabled”, which will mislead debugging when start-comment deletion
fails under on_failure suppression.
Code

internal/statuscomment/statuscomment.go[R172-178]

+	if !shouldPostCompletion(n.cfg.Comment.Completion, status) {
+		// Completion comment suppressed (disabled or on_failure with success) —
+		// clean up the start comment so it doesn't remain orphaned in its
+		// "Started" state.
		if n.startCommentID != 0 {
			if err := n.refreshClient(ctx); err != nil {
				n.warnf("failed to mint token for start comment cleanup: %v", err)
Relevance

●●● Strong

They’ve accepted aligning warnings/messages with actual behavior to prevent misleading debugging
output.

PR-#697

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new helper suppresses completion for on_failure when status == "success", routing through
the same cleanup block that logs "when completion disabled" warnings on errors.

internal/statuscomment/statuscomment.go[126-184]
internal/cli/run.go[665-676]

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

### Issue description
When completion comments are suppressed (either `disabled` or `on_failure` on `success`), the start-comment cleanup path can emit warnings. Those warnings currently hardcode wording for the `disabled` case, which is now inaccurate for the new `on_failure` suppression path.

### Issue Context
This affects only warning text (emitted on error), but it is triggered by the new `on_failure` behavior added in this PR.

### Fix Focus Areas
- internal/statuscomment/statuscomment.go[126-184]

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


3. Outdated config GoDoc ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
The GoDoc for CommentNotificationConfig still describes only enabled/disabled, but this PR
adds on_failure as a valid value for comment.completion, leaving the type documentation
incorrect.
Code

internal/config/config.go[R457-459]

+	validCompletionValues := []string{"", "enabled", "disabled", "on_failure"}
+	if !slices.Contains(validCompletionValues, cfg.Comment.Completion) {
+		return fmt.Errorf("invalid status_notifications.comment.completion %q: must be \"enabled\", \"on_failure\", or \"disabled\"", cfg.Comment.Completion)
Relevance

●●● Strong

They routinely update GoDoc/comments when semantics change to keep config types accurate.

PR-#5625

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The validation change explicitly allows on_failure, while the nearby type comment still lists only
the old values.

internal/config/config.go[108-119]
internal/config/config.go[449-461]

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

### Issue description
`validateStatusNotifications` now accepts `on_failure` for `comment.completion`, but the type-level comment on `CommentNotificationConfig` hasn’t been updated and still implies only `enabled`/`disabled` are valid.

### Issue Context
This is a documentation/maintainability issue that can mislead future maintainers and any generated/internal docs.

### Fix Focus Areas
- internal/config/config.go[114-119]
- internal/config/config.go[449-461]

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


Grey Divider

Context used
✅ Compliance rules (platform): 54 rules

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread internal/statuscomment/statuscomment.go
Comment thread docs/guides/user/customizing-agents.md Outdated
Comment thread internal/config/config.go

@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 findings (medium+ severity)

Posting the medium+ findings from a 4-agent review pass (claude-coder, claude-researcher, grok-review-agent, gemini-code-review). Two additional medium findings couldn't be attached inline since they're in files outside this PR's diff:

[MEDIUM] operations.md still documents the old enabled/disabled-only completion values
docs/guides/getting-started/operations.md:150-162 still shows completion: enabled # "enabled" (default) | "disabled", unchanged by this PR — no mention of on_failure. Meanwhile running-agents-locally.md's cross-reference was repointed to the new customizing-agents.md#status-notifications section, leaving two docs pages disagreeing about valid values for the same key. Suggest replacing operations.md's section with a pointer to the new canonical location instead of maintaining two copies.

[MEDIUM] reconcile-status/ReconcileOrphaned ignores status_notifications entirely
internal/cli/reconcilestatus.go and internal/statuscomment/statuscomment.go's ReconcileOrphaned take no config input and always finalize an orphaned marker to "Interrupted" regardless of completion's configured value. This is pre-existing behavior, but this PR turns "silent on success" into a documented guarantee without addressing the narrow race where a process succeeds but is hard-killed before its deferred PostCompletion runs — the reconciler would still surface a false "Interrupted" notice under on_failure. Worth a doc note or a test capturing this known interaction.

Comment thread internal/config/config.go
Comment thread internal/statuscomment/statuscomment.go
Comment thread internal/statuscomment/statuscomment.go
@ralphbean
ralphbean force-pushed the feat/3697-on-failure-comment-completion branch from f832f7a to 1999cd4 Compare July 29, 2026 20:48
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 29, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 8:48 PM UTC · Ended 8:49 PM UTC
Commit: f832f7a · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 29, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:50 PM UTC · Completed 9:04 PM UTC
Commit: 1999cd4 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@ralphbean
ralphbean force-pushed the feat/3697-on-failure-comment-completion branch from 20b4437 to 6131423 Compare July 29, 2026 21:36
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 29, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:38 PM UTC · Completed 9:55 PM UTC
Commit: 6131423 · View workflow run →

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

[HIGH] Required behaviour CI check is red at current HEAD — PR is currently BLOCKED

(Noting this in the review body rather than as an inline comment since the referenced test file, e2e/behaviour/features/dispatch/url-dispatch.feature:26, is not part of this PR's diff.)

The behaviour required check is failing on the PR's current HEAD (6131423) and mergeStateStatus is BLOCKED. This isn't a stale/cached result: commit 6131423 ("fix(#3697): auto-suppress start comment when completion is on_failure") triggered a fresh CI run (run 30493021917, job 90715292701) that ran to completion and failed again — this time with 4 scenarios failing (pr-ping, fork-pr-sync, url-ping, enabled-ping, all failing with a generic "did not complete successfully" error, clustered within a ~26s window around the ~13–14 minute mark of the run) versus only 1 scenario failing on the prior commit's run (1999cd47, run 30489904095).

The changing failure count/set across consecutive runs of the same suite is a strong flake/infrastructure signature (e.g. rate-limiting, a stuck dispatch queue, or the suite's own time-boxed guard pending the harness CEL cutover) rather than a deterministic regression — this PR's diff (docs, internal/config/config.go, internal/statuscomment/statuscomment.go + tests) has zero file overlap with e2e/behaviour or dispatch/harness-resolution code in either commit, which reinforces that this is very unlikely to be caused by this change.

Regardless of root cause: this is a currently-red required check on a PR that GitHub reports as BLOCKED, and it hasn't been raised elsewhere in this PR's comment/review history yet.

Suggestion: Don't merge on a red required check. Re-run behaviour once more (or loop in CI/infra to rule out GitHub API rate-limiting or runner contention) — given the failing scenario set changed between runs and now spans 4 unrelated harness scenarios simultaneously, this looks like shared test-infrastructure flakiness rather than something to fix in this PR's code, but it needs an explicit green run (or a documented infra ticket) before merging.

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot removed the ready-for-merge All reviewers approved — ready to merge label Jul 29, 2026
fullsend-ai-review[bot]

This comment was marked as outdated.

ralphbean and others added 15 commits August 5, 2026 17:11
…cations

Allow status_notifications.comment.completion to be set to "on_failure",
which posts a completion comment only when the agent fails or is
cancelled. On success the start comment is silently removed. This
reduces notification noise while still surfacing failures.

- Extend config validation to accept "on_failure" for completion fields
  (rejected for start fields where there is no outcome yet)
- Add shouldPostCompletion() helper that evaluates on_failure against
  the agent outcome status
- Replace commentEnabled() with shouldPostCompletion() in PostCompletion
- Add unit tests covering all on_failure × status combinations
- Update operations.md to document the new option

Part of #3697 (phase 1 — comment changes only; reaction support is a
follow-up)

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
The status_notifications docs were in operations.md (infrastructure
guide). Move them to customizing-agents.md where users configure agent
behavior, and update the cross-reference from running-agents-locally.md.

Also revert the on_failure addition from operations.md — the
authoritative docs now live in the user guide.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…e operations.md

Update CommentNotificationConfig doc comment to list the valid values
per field now that start and completion accept different sets. Replace
the duplicated status notifications prose in operations.md with a
cross-reference to the canonical section in customizing-agents.md.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
When completion is set to on_failure, posting a start comment and then
deleting it on success still triggers a GitHub notification pointing to
a deleted comment — defeating the purpose of reducing noise. Now the
start comment is automatically suppressed regardless of the start
setting when completion is on_failure.

Also fixes the cleanup warning message to say "suppressed" instead of
"disabled" (covers both cases), and clarifies docs that
status_notifications is org-level only.

Signed-off-by: Ralph Bean <rbean@redhat.com>
Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Two review-driven fixes:

1. shouldPostCompletion used status != "success", so "skipped" runs
   triggered completion comments under on_failure — contradicting the
   documented behavior ("only on failure or cancellation"). Tighten to
   an allowlist: failure, cancelled, timeout.

2. on_failure suppresses the start comment marker, so ReconcileOrphaned
   could not detect hard-kills (SIGKILL/OOM) — the process death went
   completely silent. Teach ReconcileOrphaned to accept completionMode
   and synthesize an "Interrupted" comment when on_failure is configured
   and no marker is found. Plumb --fullsend-dir through reconcile-status
   so it can load the org config.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
ReconcileOrphaned synthesized false "Interrupted" comments on every
successful run with on_failure completion mode. The flow: PostStart
suppressed (no marker) → agent succeeds → PostCompletion suppressed →
reconcile finds no marker → creates false "Interrupted" comment.

Pass job status through action.yml → CLI → ReconcileOrphaned and skip
synthesis when the job succeeded — a missing marker then means the agent
completed normally, not that it was hard-killed.

Also: log warning on config load errors instead of swallowing silently,
add --fullsend-dir and --job-status to CLI docs, mention timeout in
on_failure docs.

Addresses review feedback on #5736
Signed-off-by: Ralph Bean <rbean@redhat.com>
Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
When --job-status is omitted, jobStatus defaults to an empty string
which satisfies != "success" and would trigger spurious synthesis of
an "Interrupted" comment. Add an empty-string check so synthesis only
fires when we actually know the job failed.

Signed-off-by: Ralph Bean <rbean@redhat.com>
Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
When config loading fails in reconcile-status, the warning now mentions
that the default completion mode will be used.

Signed-off-by: Ralph Bean <rbean@redhat.com>
Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Adds TestPostCompletion_OnFailure_PostsOnTimeout to exercise the
timeout status under on_failure completion mode, closing a test
gap flagged in review.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
No production code path ever assigns status="timeout" — run.go maps
context.DeadlineExceeded to "cancelled" via ctx.Err(). Remove the
dead branch from shouldPostCompletion, drop the test that exercised
it directly with a synthetic value, and update the user-facing docs
to match.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Extract reconcileOrphaned into a package-level func var (matching the
existing pattern for reconcileMintToken and reconcileNewForgeClient)
so CLI tests can stub it and assert the completionMode plumbing.

Three new tests cover:
- valid org config with on_failure: mode is passed through
- malformed config.yaml: warning emitted, falls back to empty mode
- missing config.yaml (MissingOK): falls back to empty mode

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Addresses waynesun09's review on internal/cli/reconcilestatus.go: when
--fullsend-dir is set but the loaded config doesn't satisfy
OrgConfigReader (or StatusNotifications() is nil), completionMode
silently stayed "" with no diagnostic. Now logs an INFO line so
operators can distinguish "not an org config" from "org config
loaded, on_failure just isn't configured" when debugging why
Interrupted comments never appear.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
job.status is read at the point the "Finalize orphaned status
comment" step executes. If a later always() step (e.g. artifact
upload) fails after the agent succeeded, job.status was already
captured as success, so on_failure mode never synthesizes the
interrupted comment and the run looks clean despite ultimately
failing. Move Finalize to run last among the always() steps so
job.status reflects the job's true final outcome.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
shouldPostCompletion treated a "skipped" status (set when a
pre-script determines no work is needed) the same as success under
on_failure mode, so skipped runs produced zero comments — no start
(auto-suppressed) and no completion. That silently discards the
skip reason the pre-script feature exists to surface. Treat
"skipped" as a case that should post under on_failure, alongside
failure and cancelled, and document it in the completion-modes
table.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Comment thread internal/statuscomment/statuscomment.go Outdated
@ralphbean
ralphbean force-pushed the feat/3697-on-failure-comment-completion branch from efad69d to d6dcdc9 Compare August 5, 2026 21:44
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 5, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:45 PM UTC · Completed 10:06 PM UTC
Commit: d6dcdc9 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@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 findings (medium severity)

[MEDIUM] on_failure crash-visibility guarantee is GitHub-only; GitLab CI scaffold has no equivalent reconciliation step
internal/scaffold/fullsend-repo-gitlab/.gitlab/ci/fullsend-agent.yml (not modified by this PR, so no inline diff line is available)

The on_failure crash-visibility guarantee (ReconcileOrphaned synthesizing an "Interrupted" comment when a hard-killed process leaves no marker) is wired up only in action.yml's "Finalize orphaned status comment" step, which calls fullsend reconcile-status --fullsend-dir ... --job-status .... There's no equivalent reconcile-status (or reconcile) invocation anywhere under internal/scaffold/fullsend-repo-gitlab/.gitlab/ci/. The GitLab scaffold job runs fullsend run directly under set -euo pipefail with no after_script/cleanup stage.

Since on_failure suppresses the start-comment marker entirely (statuscomment.go's PostStart: commentEnabled(n.cfg.Comment.Start) && n.cfg.Comment.Completion != "on_failure"), a hard-killed fullsend process under completion: on_failure on GitLab CI produces zero visible trace of the failure — worse than the pre-existing gap for other completion modes (which at least leave a stale "Started" comment). docs/guides/user/customizing-agents.md documents on_failure ("successful runs leave no trace, but failures still surface") with no platform caveat about this GitHub-only synthesis.

Suggestion: either wire an equivalent reconciliation call into the GitLab scaffold (e.g., an after_script/on-failure stage invoking fullsend reconcile-status --fullsend-dir .fullsend --job-status "$CI_JOB_STATUS"), or explicitly document in customizing-agents.md that on_failure's hard-kill visibility guarantee currently only applies to the GitHub Actions composite action.

…nt name

Two findings from review round on PR #5736:

- waynesun09 (HIGH): ReconcileOrphaned's synthesis guard treated
  jobStatus=="success" as proof nothing needs posting. But a skipped run
  whose PostCompletionWithDetail call itself fails also reports
  jobStatus=="success" (the post error is only logged, never surfaces
  as a job failure), so the skip reason was silently lost. Thread
  action.yml's outputs.skipped through as --was-skipped, and allow
  synthesis when wasSkipped is true regardless of jobStatus.

- waynesun09 (MEDIUM): the synthesized "Interrupted" comment had no
  agent identity, so in a repo running multiple agents against the
  same issue/PR there was no way to tell which one failed. Thread the
  --role value through as an agent description used for the comment
  heading, matching the formatting already used for start/completion
  comments in run.go.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
// cancelled, or the run was skipped and its own skip-reason comment
// failed to post. A successful, non-skipped job with no marker means
// PostCompletion suppressed the comment as designed. See PR #5736.
if completionMode == "on_failure" && (wasSkipped || (jobStatus != "" && jobStatus != "success")) {

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] Synthesized comment mislabels a successful-but-skipped run as "Terminated"

When wasSkipped is true and no marker comment is found (the pre-script decided to skip and PostCompletionWithDetail's own skip-reason comment failed to post — its error is only logged, not propagated to the job's exit code, per the code comment at line 512), ReconcileOrphaned synthesizes a comment via buildInterruptedBody(..., reason). reason defaults to ReasonTerminated (internal/cli/reconcilestatus.go's --reason flag defaults to "terminated", and action.yml only passes --reason cancelled when JOB_STATUS=="cancelled" — see action.yml lines 479-480). reasonLabel() then renders "❌ Terminated" for any non-cancelled reason.

So a run that completed fully successfully, where only the skip-reason comment itself failed to post, will surface an "Interrupted ... Terminated" comment on the issue/PR — indistinguishable from an actual hard-kill/crash. This is confirmed by the PR's own new test, TestReconcileOrphaned_OnFailure_SynthesizesWhenSkippedEvenIfJobSucceeded (statuscomment_test.go:694-708), which explicitly asserts assert.Contains(comments[0].Body, "❌ Terminated") for this exact skip-success scenario.

This wasSkipped synthesis path was only just introduced in the latest commit (ce5879e), and this mislabeling hasn't been raised yet.

Suggestion: Give the wasSkipped-but-jobStatus-success synthesis branch its own reason/label (e.g. a new TerminationReason like ReasonSkipCommentFailed rendering "⚠️ Skipped (comment failed to post)") instead of falling through to the generic "Terminated" reason, since the underlying event (a successful run whose own comment-post attempt failed) is semantically very different from a hard-kill or cancellation and would otherwise mislead an operator into thinking the agent crashed.

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 7, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:34 PM UTC · Completed 2:54 PM UTC

Commit: d16ed23 · 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.

Comment thread action.yml
RUN_ID: ${{ github.run_id }}
RUN_URL: ${{ inputs.run-url }}
JOB_STATUS: ${{ job.status }}
WAS_SKIPPED: ${{ steps.run.outputs.skipped }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] logic-error

The reconcile step passes ${{ job.status }} as JOB_STATUS, but job.status reflects the cumulative outcome of ALL prior steps in the job. This PR moves the Upload fullsend artifacts step before the reconcile step (both use if: always()), so if the upload fails after a successful fullsend run, job.status will be 'failure' and ReconcileOrphaned will synthesize a spurious 'Interrupted' comment — falsely claiming the agent was terminated when it completed normally.

Suggested fix: Pass ${{ steps.run.outcome }} instead of ${{ job.status }} for the JOB_STATUS env var.

// to a deleted comment — defeating the purpose of reducing noise.
func (n *Notifier) PostStart(ctx context.Context, description string) error {
n.startTime = n.now().UTC()

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] edge-case

PostStart suppresses the start comment when completion is on_failure by checking n.cfg.Comment.Completion != 'on_failure'. A user who sets start: enabled and completion: on_failure will not see a start comment, which may be surprising. The behavior is deliberate and documented: posting then deleting a start comment on success still triggers a GitHub notification pointing to a deleted comment, which defeats the noise-reduction purpose.

// PostCompletion could run. Synthesize an "Interrupted" comment so the
// failure is visible — but only when the job actually failed or was
// cancelled, or the run was skipped and its own skip-reason comment
// failed to post. A successful, non-skipped job with no marker means

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] edge-case

The synthesis condition completionMode == 'on_failure' && (wasSkipped || (jobStatus != '' && jobStatus != 'success')) accepts any non-empty, non-'success' jobStatus value. Any unexpected value (e.g., a typo in the --job-status flag) would trigger synthesis. The risk is low since the flag value is controlled by action.yml, not user input.

// mechanism (e.g., a GitHub Actions post-job step) that runs even when the
// fullsend process is killed. It does not require a Notifier instance since
// the process that created it is gone.
//

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] api-shape

ReconcileOrphaned now has 13 positional parameters (up from 9), adding completionMode, jobStatus, wasSkipped, and agentDescription. The codebase uses options struct patterns elsewhere (e.g., config.LoadOpts). Pre-existing concern worsened by four parameters.

└── --forge <platform> # Forge platform (github, gitlab); auto-detected from CI env
├── --forge <platform> # Forge platform (github, gitlab); auto-detected from CI env
├── --fullsend-dir <path> # Path to fullsend config directory (completion mode detection)
└── --job-status <string> # Job outcome from CI runner (e.g. success, failure, cancelled)

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] missing-doc

The --was-skipped flag was added to the reconcile-status command but was not added to the CLI tree documentation. The PR added --fullsend-dir and --job-status to the CLI tree but omitted --was-skipped.

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

Labels

component/docs User-facing documentation fullsend-fix Enables automatic bot-triggered fix runs on human-authored PRs type/feature New capability request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants