Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
87f9eca
feat(#3697): add on_failure mode for comment.completion status notifi…
ralphbean Jul 29, 2026
e5d30a7
docs(#3697): move status notifications to user-facing config guide
ralphbean Jul 29, 2026
819e6c3
docs(#3697): fix stale status notification doc comment and deduplicat…
ralphbean Jul 29, 2026
75a56e1
fix(#3697): auto-suppress start comment when completion is on_failure
ralphbean Jul 29, 2026
b95181e
fix(#3697): tighten on_failure predicate and fix orphan reconciliation
ralphbean Jul 30, 2026
970d562
fix: prevent false interrupted comments on successful on_failure runs
fullsend-ai-coder[bot] Jul 30, 2026
8a2f328
fix: uppercase WARNING prefix to match codebase convention
ralphbean Jul 30, 2026
6c66146
fix(#3697): guard against empty jobStatus in ReconcileOrphaned
ralphbean Jul 30, 2026
31e54c5
fix: include fallback context in config loading warning
ralphbean Jul 30, 2026
33b3d1a
test: add on_failure + timeout coverage for shouldPostCompletion
ralphbean Jul 30, 2026
f92c3dc
fix: remove unreachable "timeout" status from on_failure allowlist
ralphbean Jul 30, 2026
2a8c0c7
test: add CLI-level tests for --fullsend-dir config loading
ralphbean Jul 30, 2026
96dc517
fix: log diagnostic when reconcile-status config isn't org-shaped
ralphbean Jul 31, 2026
e9d2488
fix: run status-comment Finalize step after other always() steps
ralphbean Jul 31, 2026
db5c82b
fix: post on_failure completion comment for skipped runs
ralphbean Jul 31, 2026
e73c338
fix: synthesize interrupted comment on skip-post failure, include age…
ralphbean Aug 6, 2026
7100f1c
fix: update stale ReconcileOrphaned call missing new params
ralphbean Aug 10, 2026
8311887
docs: add missing --was-skipped flag to reconcile-status CLI tree
ralphbean Aug 10, 2026
b4fec87
fix: distinguish skip-comment-post-failure from hard termination
ralphbean Aug 10, 2026
46c5d58
fix(reconcile-status): use ConfigWriter.StatusNotifications() directly
ralphbean Aug 10, 2026
9199342
fix: soften skip-comment label and synthesize orphan comments in defa…
ralphbean Aug 10, 2026
fa6d15a
docs: document enabled-mode orphan comment synthesis
ralphbean Aug 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,16 @@ runs:
"${STATUS_FLAGS[@]+"${STATUS_FLAGS[@]}"}" \
"${MINT_FLAGS[@]+"${MINT_FLAGS[@]}"}"

- name: Upload fullsend artifacts
if: always() && inputs.agent != '__install_only__'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: fullsend-${{ inputs.agent }}
path: ${{ github.workspace }}/output

- name: Finalize orphaned status comment
# Runs last (after all other always() steps) so job.status reflects
# the job's true final outcome, not just the fullsend run outcome.
if: always() && inputs.agent != '__install_only__' && inputs.status-repo != '' && inputs.status-number != '' && inputs.mint-url != ''
shell: bash
env:
Expand All @@ -420,7 +429,9 @@ runs:
RUN_ID: ${{ github.run_id }}
RUN_URL: ${{ inputs.run-url }}
JOB_STATUS: ${{ job.status }}
Comment thread
ralphbean marked this conversation as resolved.
Resolved
Comment thread
ralphbean marked this conversation as resolved.
Resolved
Comment thread
ralphbean marked this conversation as resolved.
Resolved
Comment thread
ralphbean marked this conversation as resolved.
Resolved
Comment thread
ralphbean marked this conversation as resolved.
Resolved
Comment thread
ralphbean marked this conversation as resolved.
Resolved
Comment thread
ralphbean marked this conversation as resolved.
Resolved
WAS_SKIPPED: ${{ steps.run.outputs.skipped }}
Comment thread
ralphbean marked this conversation as resolved.
Resolved
Comment thread
ralphbean marked this conversation as resolved.
Resolved
PR_HEAD_SHA_INPUT: ${{ inputs.pr-head-sha }}
FULLSEND_DIR: ${{ inputs.fullsend-dir }}
run: |
set -euo pipefail
# When the fullsend process is hard-killed (SIGKILL, OOM, segfault),
Expand Down Expand Up @@ -451,8 +462,14 @@ runs:
COMMIT_SHA="${GITHUB_SHA}"
fi

FULLSEND_DIR="${FULLSEND_DIR:-${GITHUB_WORKSPACE}}"
RECONCILE_FLAGS=(--repo "${STATUS_REPO}" --number "${STATUS_NUMBER}" --run-id "${RUN_ID}")
RECONCILE_FLAGS+=(--mint-url "${MINT_URL}" --role "${AGENT}")
RECONCILE_FLAGS+=(--fullsend-dir "${FULLSEND_DIR}")
RECONCILE_FLAGS+=(--job-status "${JOB_STATUS}")
Comment thread
ralphbean marked this conversation as resolved.
Resolved
if [[ "${WAS_SKIPPED}" == "true" ]]; then
RECONCILE_FLAGS+=(--was-skipped)
fi
if [[ -n "${RUN_URL}" ]]; then
RECONCILE_FLAGS+=(--run-url "${RUN_URL}")
fi
Expand All @@ -465,10 +482,3 @@ runs:
fullsend reconcile-status "${RECONCILE_FLAGS[@]+"${RECONCILE_FLAGS[@]}"}" || {
echo "::warning::Could not reconcile orphaned status comment"
}

- name: Upload fullsend artifacts
if: always() && inputs.agent != '__install_only__'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: fullsend-${{ inputs.agent }}
path: ${{ github.workspace }}/output
5 changes: 4 additions & 1 deletion docs/guides/dev/cli-internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,10 @@ fullsend
├── --reason <string> # Termination reason: terminated or cancelled (default: terminated)
├── --mint-url <url> # Mint service URL for on-demand token (default: $FULLSEND_MINT_URL)
├── --role <string> # Agent role for minting (required with --mint-url)
└── --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)
└── --was-skipped # Pre-script decided to skip the run; forces synthesis under on_failure
```

### Command Decomposition
Expand Down
23 changes: 1 addition & 22 deletions docs/guides/getting-started/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,28 +176,7 @@ gcloud services enable \

## Status notifications

Agent workflows post status comments on issues and PRs when they start and complete. This behavior is controlled by the `status_notifications` section in `config.yaml`.

For per-org installs, nest it under `defaults`:

```yaml
defaults:
status_notifications:
comment:
start: enabled # "enabled" (default) | "disabled"
completion: enabled # "enabled" (default) | "disabled"
```

For per-repo installs, set it at the top level of `.fullsend/config.yaml`:

```yaml
status_notifications:
comment:
start: enabled
completion: enabled
```

When `status_notifications` is omitted, comments default to enabled.
See [Status Notifications](../user/customizing-agents.md#status-notifications) for configuring start and completion comments.

The composite action accepts four optional inputs for status notifications:

Expand Down
35 changes: 35 additions & 0 deletions docs/guides/user/customizing-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,41 @@ my-repo/
│ └── harness/code.yaml # Repo-specific harness configuration
```

## Status Notifications

Agent workflows post status comments on issues and PRs when they start and complete. Control this with `status_notifications` in `config.yaml`.

For per-org installs, nest it under `defaults`:

```yaml
defaults:
status_notifications:
comment:
start: enabled # "enabled" (default) | "disabled"
completion: enabled # "enabled" (default) | "on_failure" | "disabled"
```

For per-repo installs, set it at the top level of `.fullsend/config.yaml`:

```yaml
status_notifications:
comment:
start: enabled
completion: enabled
```

When `status_notifications` is omitted entirely, both start and completion comments default to enabled.

### Completion modes

| Value | Behavior |
|-------|----------|
| `enabled` | Always post a completion comment (default) |
| `on_failure` | Post when the agent fails, is cancelled, or is skipped by a pre-script; the start comment is automatically suppressed to avoid notification noise |
| `disabled` | Never post a completion comment; silently remove the start comment |

`on_failure` is useful when you want to reduce notification noise — successful runs leave no trace, but failures still surface. When `completion` is set to `on_failure`, the start comment is automatically suppressed regardless of the `start` setting, because posting and then deleting a start comment would still trigger a GitHub notification pointing to a deleted comment.

## Disabling Agents

To disable an agent (including built-in scaffold agents) without removing
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/user/running-agents-locally.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ fullsend run triage \
For GitLab repositories, use `--forge gitlab` instead of `--mint-url`. The agent reads `GITLAB_TOKEN` from the environment and does not require the mint service. See the [operations guide](../getting-started/operations.md#gitlab-ci) for required environment variables.

Status comment behavior is configured via `status_notifications` in
`config.yaml`. See the [operations guide](../getting-started/operations.md#status-notifications).
`config.yaml`. See [Status Notifications](customizing-agents.md#status-notifications).

## Run from a container

Expand Down
51 changes: 41 additions & 10 deletions internal/cli/reconcilestatus.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/spf13/cobra"

"github.com/fullsend-ai/fullsend/internal/config"
"github.com/fullsend-ai/fullsend/internal/forge"
gh "github.com/fullsend-ai/fullsend/internal/forge/github"
gl "github.com/fullsend-ai/fullsend/internal/forge/gitlab"
Expand All @@ -18,18 +19,22 @@ var reconcileMintToken = mintclient.MintToken
var reconcileNewForgeClient = func(token string) forge.Client {
return gh.New(token)
}
var reconcileOrphaned = statuscomment.ReconcileOrphaned

func newReconcileStatusCmd() *cobra.Command {
var (
repo string
number int
runID string
runURL string
sha string
reason string
mintURL string
role string
forgeFlag string
repo string
number int
runID string
runURL string
sha string
reason string
mintURL string
role string
forgeFlag string
fullsendDir string
jobStatus string
wasSkipped bool
)

cmd := &cobra.Command{
Expand Down Expand Up @@ -82,7 +87,30 @@ finalized, this is a no-op.`,
default:
termReason = statuscomment.ReasonTerminated
Comment thread
ralphbean marked this conversation as resolved.
Resolved
Comment thread
ralphbean marked this conversation as resolved.
Resolved
}
Comment thread
ralphbean marked this conversation as resolved.
Resolved
return statuscomment.ReconcileOrphaned(cmd.Context(), client, owner, repoName, number, runID, runURL, sha, termReason)

Comment thread
ralphbean marked this conversation as resolved.
Resolved
completionMode := ""
if fullsendDir != "" {
Comment thread
ralphbean marked this conversation as resolved.
Resolved
Comment thread
ralphbean marked this conversation as resolved.
Resolved
writer, err := config.LoadConfigWriter(fullsendDir, config.LoadOpts{MissingOK: true})
Comment thread
ralphbean marked this conversation as resolved.
Resolved
switch {
case err != nil:
fmt.Fprintf(os.Stderr, "WARNING: could not load config from %s: %v; using default completion mode\n", fullsendDir, err)
default:
// ConfigWriter embeds StatusNotificationsReader (via
// ConfigReader) directly, so this works for both org
// and per-repo configs — no need to type-assert to
// OrgConfigReader, which per-repo configs don't
// implement.
if sn := writer.StatusNotifications(); sn != nil {
completionMode = sn.Comment.Completion
} else {
fmt.Fprintf(os.Stderr, "WARNING: no status_notifications configured at %s; using default completion mode\n", fullsendDir)
}
}
}

agentDescription := titleCase(strings.ReplaceAll(role, "-", " "))

return reconcileOrphaned(cmd.Context(), client, owner, repoName, number, runID, runURL, sha, termReason, completionMode, jobStatus, wasSkipped, agentDescription)
},
}

Expand All @@ -95,6 +123,9 @@ finalized, this is a no-op.`,
cmd.Flags().StringVar(&mintURL, "mint-url", "", "mint service URL for on-demand token (default: $FULLSEND_MINT_URL)")
cmd.Flags().StringVar(&role, "role", "", "agent role for minting (required with --mint-url)")
cmd.Flags().StringVar(&forgeFlag, "forge", "", `forge platform (e.g. "github", "gitlab"); auto-detected from CI env vars when omitted`)
cmd.Flags().StringVar(&fullsendDir, "fullsend-dir", "", "path to fullsend config directory (used to detect completion mode for orphan synthesis)")
cmd.Flags().StringVar(&jobStatus, "job-status", "", "job outcome from the CI runner (e.g. success, failure, cancelled)")
cmd.Flags().BoolVar(&wasSkipped, "was-skipped", false, "whether the pre-script decided to skip the run (forces synthesis under on_failure even when --job-status is success)")
_ = cmd.MarkFlagRequired("repo")
_ = cmd.MarkFlagRequired("number")
_ = cmd.MarkFlagRequired("run-id")
Expand Down
Loading
Loading