github / github/gh-aw

Proposal: enact fallback issues and add issue-based human approval for safe outputs

Open
#61,112 2 comments 0 reactions 0 assignees View on GitHub
cli enhancement safe-outputs
Dominant language
Go
Stars
5.1k
Forks
541
Avg merge
5h 48m
Merged PRs (30d)
773

Description

## Problem

Fallback issues often preserve enough evidence to recover an operation, but they do not provide a clear, reliable next action. A maintainer must interpret prose, download an artifact, run several git commands, and reconstruct the intended write.

A concrete example is [fsprojects/Paket#4411](https://github.com/fsprojects/Paket/issues/4411). It was intended to be a pull request, but the branch push failed. The fallback issue contains the proposed PR body, source run, bundle filename, temporary refs, branch/base names, and manual shell instructions. This is valuable recovery data, but it is difficult to review and easy to execute incorrectly.

We should support an explicit operation such as:

```sh
gh aw enact https://github.com/fsprojects/Paket/issues/4411
```

The command would treat invocation as approval, show the exact pending operation, and safely continue it to the next stage. The same model could make issues or issue comments a general human-approval surface for safe outputs.

## Current fallback issue inventory

The current producers fall into three different classes:

| Producer | Why an issue is created | Intended next step | Enactable? |
| --- | --- | --- | --- |
| `create-pull-request`: git push failure, bundle or patch transport | The branch could not be pushed | Recover the artifact, create and push the branch, then create the PR | Yes |
| `create-pull-request`: protected files with `fallback-to-issue` | A human must review before a PR is opened; the branch may already exist or may require artifact recovery | Create the reviewed PR | Yes |
| `create-pull-request`: GitHub Actions PR permission denial | The branch was pushed but Actions cannot create the PR | Create the PR from the compare URL | Yes |
| `create-pull-request`: other PR creation failure | The branch was pushed but the API operation failed | Retry PR creation | Yes |
| `create-pull-request`: `max-patch-files` exceeded | Policy rejected the proposed patch | Change policy or reduce the patch and rerun | No direct enactment; this is remediation |
| `push-to-pull-request-branch`: protected files | A patch for an existing PR branch requires review | Apply and push the reviewed patch | Yes |
| `create-discussion`: discussion creation unavailable | The issue is a semantic substitute for a discussion | Usually configure Discussions or manually migrate | Usually no; the fallback itself is the completed substitute |
| agent failure and missing-tool reports | The run failed or lacked a capability | Fix configuration/permissions and rerun | No; these are diagnostics, not pending writes |

This distinction should be explicit. Not every issue created because an operation failed is an enactable proposal.

The PR-related issue bodies currently have several bespoke forms: clickable compare links when a branch exists, bundle/patch shell recipes when it does not, protected-file review text, generic API error text, and policy-remediation text. They carry useful human-readable information, but there is no common machine-readable operation record.

## Existing related mechanisms

There is substantial prior art to reuse:

- [#29269](https://github.com/github/gh-aw/pull/29269) added label-triggered maintenance actions. Applying `agentic-workflows:apply-safe-outputs` to an agent-created issue extracts the source run marker and replays safe outputs after an exact `admin` or `maintain` permission check.
- Agentic Maintenance also exposes `safe_outputs` through `workflow_dispatch`, given a source run URL or ID.
- Safe-output replay downloads `agent_output.json` and invokes the normal handler manager with fresh workflow credentials.
- `manual-approval` and GitHub Environment protection rules can pause a whole activation or a custom gate before safe-output jobs.
- `staged: true` previews writes without applying them, but has no durable later enactment step.
- `approve-workflow-run` is a safe output for one narrowly validated approval operation. It verifies the run, associated PR, status, repository allowlist, and protected files before acting.
- Integrity `approval-labels` promote input trust; they do not approve an output operation.

The existing replay path is close, but too coarse for this use case: it replays every output in a run, infers an empty handler configuration from item types, and identifies the run through editable issue prose. Approval should target one immutable operation and preserve the effective safety policy that produced it.

## Proposal

### 1. Introduce a versioned enactment record

When gh-aw defers an otherwise valid safe output, write an immutable record to the run artifact. The issue or comment is a human review surface and contains only a reference plus a readable rendering.

A record should include at least:

```json
{
"schema_version": 1,
"proposal_id": "sha256:...",
"source": {
"repository": "OWNER/REPO",
"workflow_id": "repo-assist",
"run_id": 33315175872,
"item_index": 0
},
"operation": {
"type": "create_pull_request",
"target_repository": "fsprojects/Paket",
"payload_digest": "sha256:...",
"policy_digest": "sha256:..."
},
"artifact": {
"id": 123,
"name": "agent",
"digest": "sha256:..."
},
"expires_at": "..."
}
```

The artifact contains the canonical validated payload, effective non-secret handler policy, transport metadata, and references to compiler-declared credentials. Do not embed credentials or secret values. The issue marker must be versioned and include only the proposal ID and immutable source coordinates.

Editing issue prose must never change the operation. At enactment time, fetch the artifact, verify all digests and source coordinates, and render the artifact truth for approval. If the displayed issue summary differs from the record, fail closed and explain the mismatch.

### 2. Add per-output human approval modes

Support this on built-in safe outputs, initially on the recoverable code-write outputs:

```yaml
safe-outputs:
create-pull-request:
human-approval: fallback-issue

push-to-pull-request-branch:
human-approval: fallback-issue-comment
```

Proposed values:

- omitted or `none`: current direct behavior
- `fallback-issue`: validate and stage the operation, then create a dedicated approval issue instead of applying it
- `fallback-issue-comment`: validate and stage the operation, then add an approval comment to the triggering issue or PR

`fallback-issue-comment` must require a stable issue-like target, either from the trigger or explicit configuration. If no target exists, fail closed rather than silently applying the output. A later phase can generalize the mechanism to every built-in and custom safe output once each type defines a complete, stable enactment payload.

This setting is distinct from `fallback-as-issue`: `human-approval` intentionally defers a valid operation before execution, while `fallback-as-issue` recovers after execution failed. Existing recoverable fallback issues should be migrated to emit the same enactment record.

### 3. Add `gh aw enact `

The command should:

1. Parse a GitHub issue, PR comment, or issue comment URL and fetch the proposal marker.
2. Resolve exactly one proposal. Require `--proposal` if the surface contains more than one.
3. Fetch and verify the immutable artifact record.
4. Revalidate repository, target, allowlists, limits, protected-file policy, threat-detection result, source workflow identity, expiry, and current configuration compatibility.
5. Show the exact operation, target, source run, policy-relevant details, and any drift. The command invocation is the approval; interactive use may confirm after preview, with `--yes` for automation.
6. Verify that the caller has exact `admin` or `maintain` access. For cross-repository outputs, require authorization on both the proposal repository and target repository.
7. Dispatch a generated enactment job that uses fresh credentials and the same compiler-derived safe-output configuration as the source workflow. Do not execute repository writes by interpreting shell snippets from the issue.
8. Wait for completion by default, report the created/updated resource, and post a receipt on the approval surface. Offer `--no-wait` when only dispatch is desired.

The existing maintenance replay driver and handler manager should be reused, but extended to select one proposal and consume its preserved effective configuration. A generated per-workflow enactment entry point may be preferable to a repository-wide broadly privileged job because it can retain the original permission and credential boundary.

### 4. Support GitHub-native enactment

The CLI is the first ergonomic entry point. The same backend should also allow maintainers to enact without a local CLI:

- apply an `agentic-workflows:enact` label to a dedicated proposal issue; or
- use an exact command such as `/enact ` on an approval comment.

Both paths must use the same permission check, verification, dispatch, and receipt logic as the CLI. Comment text is a trigger only, never executable input.

### 5. Make enactment idempotent and auditable

Use `proposal_id` as the durable idempotency key.

- Serialize attempts with an Actions concurrency group derived from the proposal ID.
- Before execution, look for a bot-authored success receipt for that proposal.
- After success, post a machine-readable receipt containing proposal ID, enactment run, actor, timestamp, result URL, and result identity.
- A repeated enactment returns the prior result without repeating the write.
- Record failed attempts separately and permit a retry only when the operation type declares retry safety.
- Close a dedicated proposal issue after success; do not automatically close a shared issue used by comment mode.

This must work across separate workflow runs, not only within one handler-manager process.

## Security requirements

- Preserve the safe-output trust boundary: the agent still has no write credential, and enactment uses a fresh token in a separate job.
- Never trust edited issue/comment content as an operation payload.
- Never use arbitrary run URLs as authority. Verify repository ownership, workflow identity, artifact provenance, proposal digest, and item index.
- Re-run current egress validation at enactment time. Approval does not bypass target allowlists, protected-file checks, max limits, or threat detection by default.
- Reject expired or unavailable artifacts with actionable recovery guidance.
- Keep credential references compiler-derived and secret values out of artifacts, logs, issue bodies, and receipts.
- Preserve actor attribution in the audit record.
- Define behavior for configuration drift. The safe default is to require both the recorded policy and current policy to allow the operation.

## Suggested delivery plan

1. Define and test the enactment record/marker schema and classify safe-output types as enactable, retryable, or diagnostic.
2. Make current `create-pull-request` and `push-to-pull-request-branch` fallback issues emit records while retaining existing manual instructions.
3. Add single-proposal selection, digest verification, durable receipts, and idempotency to Agentic Maintenance.
4. Add `gh aw enact` as a thin preview/authorization/dispatch/wait client.
5. Add `human-approval: fallback-issue` for the two code-write outputs.
6. Add issue-comment mode and GitHub-native label/slash-command triggers.
7. Extend type by type to other safe outputs after defining type-specific review summaries and retry semantics.

## Acceptance criteria

- Paket issue #4411 style data is sufficient to enact the intended PR without manually running artifact or git recovery commands.
- A proposal URL resolves to one immutable safe-output payload and cannot be changed by editing issue text.
- Only an authorized maintainer can enact it.
- Enactment uses fresh, compiler-declared credentials and current egress checks.
- Repeated or concurrent enactment produces at most one side effect and returns the existing result.
- The issue/comment receives a clear pending, running, succeeded, failed, or expired status and links to the enactment run/result.
- Existing fallback issue formats remain manually usable during migration.
- Diagnostic and remediation issues are not presented as enactable operations.
- Documentation clearly differentiates environment gates, staged mode, run-wide replay, failure fallback, and per-operation issue approval.

## Open questions

- Should the public configuration use the requested `human-approval` name, or a more general `delivery: direct | issue | comment` model?
- Should enactment always use a generated per-workflow job, or can maintenance jobs safely preserve distinct per-workflow credentials and permission scopes?
- What retention period is required for proposal artifacts, and should long-lived proposals copy the canonical record to another immutable store?
- For a changed base branch or target resource, which operation types may be safely rebased/revalidated and which must expire?
- Should approval of threat-detection-blocked output require a separate explicit override capability rather than ordinary enactment?

Contributor guide

Open the contributing guide

Research direction

Start with the existing maintenance replay driver, handler manager, safe-output replay, and approve-workflow-run mechanisms described in the issue. Trace the suggested delivery plan from the enactment record schema through single-proposal verification and the gh aw enact client; done means the acceptance criteria hold, including immutable payloads, authorization, fresh credentials, current checks, and idempotent receipts.

Written by the indexing model from the issue text.

Assessment

Tech stack
github-actions, go
Domain
ci-cd, cli, devops, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.