Skip to content

Action-failure expiry does not trigger maintenance workflow generation #50923

Description

@PureWeen

Summary

gh-aw v0.85.4 configures action-failure issues to expire (168 hours by default) and writes an expiration marker into each generated failure issue, but action-failure expiry is not considered when deciding whether to generate agentics-maintenance.yml.

If no workflow declares another recognized expiry source, compilation omits or deletes the maintenance workflow. The displayed action-failure deadline then has no scheduled close-expired-issues consumer. The failure handler stops reusing an expired individual report, but it does not close that issue, so expired reports can remain open while later failures create additional reports. Grouped parent failure issues also receive an expiration marker, but parent lookup does not enforce it.

I reproduced this with the public v0.85.4 CLI and confirmed the same compiler/runtime paths remain on main at 5107b7bd547ce124f69084dc623e45a539cd17f5.

Minimal reproduction

Compile this standalone workflow with v0.85.4:

---
on:
  slash_command:
    name: reproduce
    events: [issue_comment]

permissions:
  contents: read
  issues: read

safe-outputs:
  add-comment:
    max: 1
---

# Minimal reproduction

Reply with a short acknowledgement.
$ gh aw version
gh aw version v0.85.4

$ gh aw compile repro --strict --validate
✓ Compiled 1 workflow: 1 succeeded, 0 warnings

Observed results:

  1. repro.lock.yml contains:

    GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168"
  2. .github/workflows/agentics-maintenance.yml is absent.

Source trace

  1. The conclusion-job compiler always injects the configured/default action-failure expiry:
    notify_comment_conclusion_helpers.go.
  2. The public reference says maintenance.action_failure_issue_expires controls failure-issue expiration and defaults to 168 hours:
    ephemerals.md.
  3. handle_agent_failure.cjs reads that value, writes expiration markers to parent/per-run failure issues, and treats an expired individual report as non-reusable:
    handle_agent_failure.cjs,
    isReusableFailureIssue,
    and failure issue creation.
  4. scanWorkflowsForExpires only accounts for explicit discussion/issue/pull-request safe-output expiry and explicit no-op issue reporting:
    maintenance_workflow.go.
  5. When that scan returns false, the compiler omits or deletes agentics-maintenance.yml:
    maintenance_workflow.go.
  6. The scheduled close-expired-issues job is generated only as part of maintenance workflow output:
    maintenance_workflow_yaml_jobs.go.

Expected behavior

Expiration metadata and available cleanup should remain consistent without silently forcing a scheduled maintenance workflow into every repository. When no cleanup consumer will exist, failure issues should not claim they expire.

Actual behavior

A repository can receive failure issues with checked expiration metadata but no generated scheduled workflow that closes them.

Relationship to #36857

#36857 reported the same producer/consumer mismatch for no-op tracker issues. #37965 aligned maintenance generation with that no-op path. Action-failure issue expiration is a separate producer and is still absent from scanWorkflowsForExpires.

Design constraint

safe-outputs.report-failure-as-issue defaults to true, so treating the implicit 168-hour default as an unconditional maintenance trigger would generate agentics-maintenance.yml for essentially every repository with a gh-aw workflow:

The fix should preserve that opt-in posture rather than merely making action-failure expiry another always-on trigger.

Suggested implementation plan

  1. Do not use the implicit 168-hour action-failure default as an unconditional reason to generate scheduled maintenance.
  2. When no recognized expiry source generates maintenance and maintenance.action_failure_issue_expires was not explicitly configured, suppress the failure-issue expiration marker. The runtime parser may need an explicit disabled value rather than falling back to 168 hours.
  3. Treat an explicitly configured maintenance.action_failure_issue_expires value as an opt-in to enforceable expiration: generate maintenance and include that value when selecting the minimum cleanup interval.
  4. If another recognized expiry source already generates maintenance, preserve action-failure markers because the generic issue sweeper can enforce them.
  5. Add regression coverage for:
    • the implicit default with no maintenance source (no unenforceable marker and no forced schedule);
    • an explicit action-failure expiry (maintenance generated);
    • coexistence with shorter and longer safe-output expiry values;
    • explicit maintenance opt-out behavior.
  6. Verify expired per-run and grouped parent failure issues are both closed when cleanup is enabled.
  7. Cover failure-issue-repo and side-repository maintenance so cleanup runs against the repository where failure issues are created; side-repository workflows do not repair missing cleanup in the primary repository.
  8. Keep the runtime expiration marker and compile-time maintenance-generation contract synchronized.

Impact

This is an issue-lifecycle/cleanup defect, not a credential or code-execution vulnerability. It can leave stale failure-report issues open and allow additional reports to accumulate after their displayed expiry.

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions