diff --git a/.github/workflows/agents-issue-format-guard.yml b/.github/workflows/agents-issue-format-guard.yml index 089672ed6..923c71b89 100644 --- a/.github/workflows/agents-issue-format-guard.yml +++ b/.github/workflows/agents-issue-format-guard.yml @@ -202,9 +202,13 @@ jobs: fi fingerprint="$(sha256sum body.md | cut -c1-12)" marker="" + retry_marker_prefix="" + if [[ "$format_guard_attempts" -ge "$max_format_guard_attempts" ]]; then + echo "Format guard exhausted $format_guard_attempts optimizer attempts; pausing issue." + gh issue edit "$NUMBER" --repo "$GITHUB_REPOSITORY" \ + --add-label "agents:auto-pilot-pause" --remove-label "agents:format" + gh issue comment "$NUMBER" --repo "$GITHUB_REPOSITORY" --body \ + " + Automated formatting stopped after $format_guard_attempts optimizer attempts. Fix the issue body manually, then remove agents:auto-pilot-pause before retrying." + exit 0 + fi has_format_label=false if jq -e '[.labels[].name] | any(. == "agents:format")' live.json >/dev/null; then has_format_label=true @@ -280,15 +306,22 @@ jobs: --limit 20 \ --json createdAt,displayTitle \ | jq --arg since "$dispatch_attempted_at" --arg issue "#$NUMBER" \ - '[.[] | select(.createdAt >= $since and (.displayTitle | contains($issue)))] | length') + '[.[] | select(.createdAt >= $since and (.displayTitle | endswith($issue)))] | length') if [[ "${match_count:-0}" -gt 0 ]]; then accepted=true break fi done if [[ "$accepted" == true ]]; then + { + cat report.md + echo + echo "Optimizer dispatch was accepted despite a CLI error; preserving the format lease." + echo + echo "$attempt_marker" + } | gh issue comment "$NUMBER" --repo "$GITHUB_REPOSITORY" --body-file - echo "::warning::optimizer dispatch CLI failed but a matching run was accepted; preserving agents:format lease" - echo "::error::optimizer dispatch status ambiguous; no completion marker written" + echo "::error::optimizer dispatch status ambiguous; recorded the accepted attempt for the retry cap" exit 1 fi gh issue edit "$NUMBER" --repo "$GITHUB_REPOSITORY" --remove-label "agents:format" \ @@ -296,15 +329,13 @@ jobs: echo "::error::optimizer dispatch failed; no completion marker written so later runs can retry" exit 1 fi - if [[ "$trusted_marker" != true ]]; then - { - cat report.md - echo - echo "Dispatched the Agents Issue Optimizer format phase." - echo - echo "$marker" - } | gh issue comment "$NUMBER" --repo "$GITHUB_REPOSITORY" --body-file - - fi + { + cat report.md + echo + echo "Dispatched the Agents Issue Optimizer format phase (attempt $format_guard_next_attempt of $max_format_guard_attempts)." + echo + echo "$attempt_marker" + } | gh issue comment "$NUMBER" --repo "$GITHUB_REPOSITORY" --body-file - - name: Restore format completion after an unheld revalidation if: >- diff --git a/docs/ci/WORKFLOWS.md b/docs/ci/WORKFLOWS.md index 7a8ca3908..d004fcb9a 100644 --- a/docs/ci/WORKFLOWS.md +++ b/docs/ci/WORKFLOWS.md @@ -154,7 +154,7 @@ Consumer default note: `agents-pr-meta-v4.yml` is a Workflows-repo service workf * [`agents-63-issue-intake.yml`](../../.github/workflows/agents-63-issue-intake.yml) is the canonical front door. It now listens for `agent:codex` labels directly and routes both label triggers and ChatGPT sync requests through the shared normalization pipeline. * [`agents-64-verify-agent-assignment.yml`](../../.github/workflows/agents-64-verify-agent-assignment.yml) validates that labelled issues retain an approved agent assignee and publishes the verification outputs. * [`agents-issue-optimizer.yml`](../../.github/workflows/agents-issue-optimizer.yml) runs issue optimization passes when `agents:optimize` or `agents:apply-suggestions` labels are applied. -* [`agents-issue-format-guard.yml`](../../.github/workflows/agents-issue-format-guard.yml) validates opened, edited, reopened, hold/exemption-label changes, and manual dispatch against `AGENT_ISSUE_FORMAT`; durable/wontfix and bot issues remain exempt. The validator requires the `Tasks` and `Acceptance Criteria` structure plus concrete task/verification evidence, preserves nested Markdown subsections, and reports `Why`, `Scope`, `Implementation Notes`, and `Non-Goals` as advisory. `agents:auto-pilot-pause` and `needs-human` hold optimizer dispatch. Any invalid non-exempt issue change on those triggers (opened, edited, reopened, eligible hold/exemption label events, or manual dispatch) clears a stale `agents:formatted` marker; hold removal revalidates before restoring it. Non-conforming unheld work is explicitly dispatched to the optimizer's `format` phase. +* [`agents-issue-format-guard.yml`](../../.github/workflows/agents-issue-format-guard.yml) validates opened, edited, reopened, hold/exemption-label changes, and manual dispatch against `AGENT_ISSUE_FORMAT`; durable/wontfix and bot issues remain exempt. The validator requires the `Tasks` and `Acceptance Criteria` structure plus concrete task/verification evidence, preserves nested Markdown subsections, and reports `Why`, `Scope`, `Implementation Notes`, and `Non-Goals` as advisory. `agents:auto-pilot-pause` and `needs-human` hold optimizer dispatch. Any invalid non-exempt issue change on those triggers (opened, edited, reopened, eligible hold/exemption label events, or manual dispatch) clears a stale `agents:formatted` marker; hold removal revalidates before restoring it. Non-conforming unheld work is dispatched to the optimizer's `format` phase at most three times for one unchanged body; each accepted dispatch is durably counted, then the guard applies `agents:auto-pilot-pause`, releases `agents:format`, and tells an operator to repair the issue body before removing the pause label to retry. * [`agents-moderate-connector.yml`](../../.github/workflows/agents-moderate-connector.yml) moderates connector-authored PR comments, enforcing repository allow/deny lists and applying the debugging label when deletions occur. * [`agents-guard.yml`](../../.github/workflows/agents-guard.yml) applies repository-level guardrails before agent workflows run. * [`pr-46-dependency-repair-contract.yml`](../../.github/workflows/pr-46-dependency-repair-contract.yml) keeps clean Renovate/Dependabot branches bot-owned and validates the bot-delta first commit on agent-owned dependency repair promotions. See [`docs/ops/DEPENDENCY_REPAIR_PROMOTION.md`](../ops/DEPENDENCY_REPAIR_PROMOTION.md) for the lane-selection contract and marker. diff --git a/langsmith-fleet-worker-attempt.json b/langsmith-fleet-worker-attempt.json index 65173a280..7b15a95be 100644 --- a/langsmith-fleet-worker-attempt.json +++ b/langsmith-fleet-worker-attempt.json @@ -1,13 +1,13 @@ { "agent": "codex", "cli_version": "0.144.1", - "emitted_at": "2026-08-08T23:54:44.735714Z", + "emitted_at": "2026-08-09T02:16:27.270949Z", "execution_profile": "codex-default", "fallback_models": [ "gpt-5.5" ], "operation_role": "worker", - "pr_number": "2997", + "pr_number": "2998", "requested_model": "gpt-5.6-terra", "runner": "reusable-codex-run", "schema": "langsmith-fleet/v1", diff --git a/templates/consumer-repo/.github/workflows/agents-issue-format-guard.yml b/templates/consumer-repo/.github/workflows/agents-issue-format-guard.yml index 089672ed6..923c71b89 100644 --- a/templates/consumer-repo/.github/workflows/agents-issue-format-guard.yml +++ b/templates/consumer-repo/.github/workflows/agents-issue-format-guard.yml @@ -202,9 +202,13 @@ jobs: fi fingerprint="$(sha256sum body.md | cut -c1-12)" marker="" + retry_marker_prefix="" + if [[ "$format_guard_attempts" -ge "$max_format_guard_attempts" ]]; then + echo "Format guard exhausted $format_guard_attempts optimizer attempts; pausing issue." + gh issue edit "$NUMBER" --repo "$GITHUB_REPOSITORY" \ + --add-label "agents:auto-pilot-pause" --remove-label "agents:format" + gh issue comment "$NUMBER" --repo "$GITHUB_REPOSITORY" --body \ + " + Automated formatting stopped after $format_guard_attempts optimizer attempts. Fix the issue body manually, then remove agents:auto-pilot-pause before retrying." + exit 0 + fi has_format_label=false if jq -e '[.labels[].name] | any(. == "agents:format")' live.json >/dev/null; then has_format_label=true @@ -280,15 +306,22 @@ jobs: --limit 20 \ --json createdAt,displayTitle \ | jq --arg since "$dispatch_attempted_at" --arg issue "#$NUMBER" \ - '[.[] | select(.createdAt >= $since and (.displayTitle | contains($issue)))] | length') + '[.[] | select(.createdAt >= $since and (.displayTitle | endswith($issue)))] | length') if [[ "${match_count:-0}" -gt 0 ]]; then accepted=true break fi done if [[ "$accepted" == true ]]; then + { + cat report.md + echo + echo "Optimizer dispatch was accepted despite a CLI error; preserving the format lease." + echo + echo "$attempt_marker" + } | gh issue comment "$NUMBER" --repo "$GITHUB_REPOSITORY" --body-file - echo "::warning::optimizer dispatch CLI failed but a matching run was accepted; preserving agents:format lease" - echo "::error::optimizer dispatch status ambiguous; no completion marker written" + echo "::error::optimizer dispatch status ambiguous; recorded the accepted attempt for the retry cap" exit 1 fi gh issue edit "$NUMBER" --repo "$GITHUB_REPOSITORY" --remove-label "agents:format" \ @@ -296,15 +329,13 @@ jobs: echo "::error::optimizer dispatch failed; no completion marker written so later runs can retry" exit 1 fi - if [[ "$trusted_marker" != true ]]; then - { - cat report.md - echo - echo "Dispatched the Agents Issue Optimizer format phase." - echo - echo "$marker" - } | gh issue comment "$NUMBER" --repo "$GITHUB_REPOSITORY" --body-file - - fi + { + cat report.md + echo + echo "Dispatched the Agents Issue Optimizer format phase (attempt $format_guard_next_attempt of $max_format_guard_attempts)." + echo + echo "$attempt_marker" + } | gh issue comment "$NUMBER" --repo "$GITHUB_REPOSITORY" --body-file - - name: Restore format completion after an unheld revalidation if: >- diff --git a/tests/workflows/test_agents_issue_optimizer_format_trigger.py b/tests/workflows/test_agents_issue_optimizer_format_trigger.py index d2fa6b9c3..3968e6c9c 100644 --- a/tests/workflows/test_agents_issue_optimizer_format_trigger.py +++ b/tests/workflows/test_agents_issue_optimizer_format_trigger.py @@ -60,16 +60,53 @@ def test_format_guard_deduplicates_inflight_optimizer_dispatch() -> None: assert "paginateWithRetry" in text assert "github.rest.issues.listComments" in text assert "per_page: 100" in text + assert "format_guard_attempts" in text + assert "max_format_guard_attempts=3" in text + assert "format_guard_next_attempt=$((format_guard_attempts + 1))" in text + assert "FORMAT_GUARD_RETRY_PREFIX" in text + assert "isCurrentAttemptMarker" in text + assert ".displayTitle | endswith($issue)" in text + assert ".displayTitle | contains($issue)" not in text + assert "agents:auto-pilot-pause" in text + assert "Automated formatting stopped after" in text + assert "" in text + assert text.index("max_format_guard_attempts=3") < text.index( + "gh workflow run agents-issue-optimizer.yml" + ) + assert text.index('"$format_guard_attempts" -ge "$max_format_guard_attempts"') < text.index( + "gh workflow run agents-issue-optimizer.yml" + ) assert "github_token: ${{ github.token }}" in text assert "secrets: ${{ toJSON(secrets) }}" not in text assert '"$trusted_marker" == true && "$has_format_label" == true' in text assert "already routed and in flight; skipping duplicate dispatch" in text - # Trusted marker is written only after a successful workflow_dispatch. + # Every accepted dispatch records a distinct marker, so a retry after the + # format lease is released still consumes the bounded retry budget. assert text.index("gh workflow run agents-issue-optimizer.yml") < text.index( - 'echo "$marker"' + 'echo "$attempt_marker"' ) +def test_format_guard_attempt_marker_sequence_consumes_retry_budget() -> None: + """Exercise the marker contract used by the workflow's routing step.""" + fingerprint = "abc123def456" + marker = f"" + retry_prefix = f"")) + comments.append(("github-actions[bot]", f"{retry_prefix}3 -->")) + comments.append(("outside-user", f"{retry_prefix}4 -->")) + assert count_accepted_attempts(comments) == 3 + + def test_format_lease_is_required_and_released_after_failure() -> None: guard = GUARD_PATH.read_text(encoding="utf-8") consumer_guard = CONSUMER_GUARD_PATH.read_text(encoding="utf-8") @@ -90,7 +127,7 @@ def test_format_lease_is_required_and_released_after_failure() -> None: assert "preserving agents:format lease" in failure_block assert '--remove-label "agents:format"' in failure_block assert '|| echo "::warning::could not release failed agents:format lease"' in failure_block - assert "no completion marker written so later runs can retry" in failure_block + assert "recorded the accepted attempt for the retry cap" in failure_block for text in ( WORKFLOW_PATH.read_text(encoding="utf-8"),