diff --git a/.github/scripts/github-api-with-retry.js b/.github/scripts/github-api-with-retry.js index 9a13c47e6..99231c2e1 100755 --- a/.github/scripts/github-api-with-retry.js +++ b/.github/scripts/github-api-with-retry.js @@ -170,7 +170,7 @@ function logWithCore(core, level, message) { core[level](message); return; } - const logFn = level === 'error' ? console.error : level === 'warning' ? console.warn : console.log; + const logFn = level === 'error' ? console.error : level === 'warning' ? console.warn : console.error; logFn(message); } @@ -428,7 +428,7 @@ async function withRetry(fn, options = {}) { ? 'rate limit' : 'transient error'; - console.log( + console.error( `${retryReason} (attempt ${attempt + 1}/${maxRetries + 1}). ` + `Retrying in ${Math.round(actualDelay / 1000)}s...` ); diff --git a/.github/workflows/agents-issue-format-guard.yml b/.github/workflows/agents-issue-format-guard.yml index a4b692e27..3acb8f601 100644 --- a/.github/workflows/agents-issue-format-guard.yml +++ b/.github/workflows/agents-issue-format-guard.yml @@ -39,6 +39,13 @@ jobs: with: persist-credentials: false + - name: Setup API client + uses: ./.github/actions/setup-api-client + with: + # This guard only reads issue comments with the workflow token. Do not + # expose the repository-wide secret bundle to the composite action. + github_token: ${{ github.token }} + - name: Resolve issue id: issue env: @@ -132,35 +139,66 @@ jobs: fi fingerprint="$(sha256sum body.md | cut -c1-12)" marker="" - # Only trust exact HTML markers authored by automation bots (not user text). - trusted_marker=false - if gh issue view "$NUMBER" --repo "$GITHUB_REPOSITORY" --json comments \ - --jq '.comments[] | select((.author.login // "") | test("\\[bot\\]$|github-actions"; "i")) | .body' \ - | grep -qF "$marker"; then - trusted_marker=true - fi + # Only trust exact HTML markers authored by github-actions[bot] + # (immutable account id 41898282 when the REST payload includes user.id). + trusted_marker="$(FORMAT_GUARD_MARKER="$marker" node - <<'NODE' + (async () => { + const { Octokit } = require('@octokit/rest'); + const { createTokenAwareRetry } = require('./.github/scripts/github-api-with-retry.js'); + const github = new Octokit({ auth: process.env.GH_TOKEN }); + const core = { info: () => {}, warning: console.warn, debug: () => {} }; + const { paginateWithRetry } = await createTokenAwareRetry({ + github, + core, + env: process.env, + task: 'issue-format-guard', + capabilities: ['issues:read'], + }); + const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/'); + const comments = await paginateWithRetry( + github.rest.issues.listComments, + { owner, repo, issue_number: Number(process.env.NUMBER), per_page: 100 } + ); + const trusted = comments.some((comment) => + comment.user?.login === 'github-actions[bot]' + && (comment.user?.id == null || comment.user.id === 41898282) + && (comment.body || '').includes(process.env.FORMAT_GUARD_MARKER) + ); + process.stdout.write(trusted ? 'true' : 'false'); + })().catch((error) => { + console.error(error); + process.exit(1); + }); + NODE + )" has_format_label=false if jq -e '[.labels[].name] | any(. == "agents:format")' live.json >/dev/null; then has_format_label=true fi - # Dedup completed handoffs, but retry when a prior dispatch/optimizer run - # left agents:format set (marker without a finished optimizer pass). + # The marker is written only after dispatch succeeds. Keep the label as + # the in-flight lease: a repeated guard run must not enqueue another + # optimizer while that lease is still present. If the label was removed, + # retry the dispatch because the earlier handoff no longer owns the work. dispatch=true - if [[ "$trusted_marker" == true && "$has_format_label" != true ]]; then - echo "Identical invalid body was already routed; skipping duplicate dispatch." + if [[ "$trusted_marker" == true && "$has_format_label" == true ]]; then + echo "Identical invalid body is already routed and in flight; skipping duplicate dispatch." dispatch=false - elif [[ "$trusted_marker" == true && "$has_format_label" == true ]]; then - echo "Prior format-guard marker present but agents:format still set — retrying optimizer dispatch." + elif [[ "$trusted_marker" == true ]]; then + echo "Prior format-guard marker present but agents:format is absent — retrying optimizer dispatch." fi if jq -e '[.labels[].name] | any(. == "agents:formatted")' live.json >/dev/null; then gh issue edit "$NUMBER" --repo "$GITHUB_REPOSITORY" --remove-label "agents:formatted" \ || echo "::warning::could not remove agents:formatted" fi - gh issue edit "$NUMBER" --repo "$GITHUB_REPOSITORY" --add-label "agents:format" \ - || echo "::warning::could not apply agents:format (label missing in this repo?)" if [[ "$dispatch" != true ]]; then exit 0 fi + # Acquiring the label is the handoff lease. Do not dispatch without it: + # otherwise a trusted marker alone could repeat the optimizer dispatch. + if ! gh issue edit "$NUMBER" --repo "$GITHUB_REPOSITORY" --add-label "agents:format"; then + echo "::error::could not acquire agents:format lease; refusing optimizer dispatch" + exit 1 + fi # GITHUB_TOKEN label edits do not start issues:labeled workflows; dispatch is explicit. # Persist the completion marker only after a successful workflow_dispatch so a # failed run remains retryable on the next guard pass. diff --git a/.github/workflows/agents-issue-optimizer.yml b/.github/workflows/agents-issue-optimizer.yml index 7d4ddbc2e..501bccb86 100644 --- a/.github/workflows/agents-issue-optimizer.yml +++ b/.github/workflows/agents-issue-optimizer.yml @@ -22,7 +22,7 @@ concurrency: group: >- agents-issue-optimizer-${{ github.repository }}-${{ - github.event.issue.number || github.run_id }} + github.event.issue.number || inputs.issue_number || github.run_id }} cancel-in-progress: false jobs: @@ -106,11 +106,6 @@ jobs: with: node-version: 24 - - name: Install load balancer dependencies - if: steps.check.outputs.should_run == 'true' - run: | - set -euo pipefail - npm install --no-save --no-package-lock @octokit/rest @octokit/auth-app - name: Setup API client if: steps.check.outputs.should_run == 'true' @@ -354,7 +349,7 @@ jobs: }); NODE - python - <<'PY' || true + python - <<'PY' import json from scripts.langchain import issue_dedup @@ -362,33 +357,37 @@ jobs: issue = json.load(f) with open('/tmp/open_issues.json', encoding='utf-8') as f: open_issues = json.load(f) - with open('/tmp/dedup_comments.json', encoding='utf-8') as f: + with open('/tmp/dedup_comments.json', encoding='utf-8') as f: comments = json.load(f) - marker = issue_dedup.SIMILAR_ISSUES_MARKER - for comment in comments or []: + marker = issue_dedup.SIMILAR_ISSUES_MARKER + for comment in comments or []: body = (comment or {}).get('body') or '' if marker in body: - raise SystemExit(0) + raise SystemExit(0) # Conservative defaults; can be tuned later. threshold = 0.82 - store = issue_dedup.build_issue_vector_store(open_issues) - if store is None: + store = issue_dedup.build_issue_vector_store(open_issues) + if store is None: raise SystemExit(0) - title = (issue.get('title') or '').strip() - body = (issue.get('body') or '').strip() - query = f"{title}\n{body}".strip() if body else title - if not query: + title = (issue.get('title') or '').strip() + body = (issue.get('body') or '').strip() + query = f"{title}\n{body}".strip() if body else title + if not query: raise SystemExit(0) - matches = issue_dedup.find_similar_issues(store, query, threshold=threshold, k=5) - comment = issue_dedup.format_similar_issues_comment(matches, max_items=5) - if comment: + matches = issue_dedup.find_similar_issues(store, query, threshold=threshold, k=5) + comment = issue_dedup.format_similar_issues_comment(matches, max_items=5) + if comment: with open('/tmp/dedup_comment.md', 'w', encoding='utf-8') as out: - out.write(comment) + out.write(comment) PY + dedup_rc=$? + if [[ $dedup_rc -ne 0 ]]; then + echo "::warning::issue dedup python exited with $dedup_rc; continuing without similar-issues comment" + fi if [[ -f /tmp/dedup_comment.md ]]; then node - <<'NODE' @@ -510,7 +509,7 @@ jobs: # Do not publish agents:formatted for apply-phase output that fails # the same fleet contract enforced by the issue-event guard. - python .github/scripts/issue_format.py /tmp/updated_body.md + python3 .github/scripts/issue_format.py /tmp/updated_body.md # Update issue body node - <<'NODE' @@ -580,7 +579,7 @@ jobs: # Do not advertise a formatted issue until it satisfies the same # fleet contract enforced by the issue-event guard. - python .github/scripts/issue_format.py /tmp/formatted_body.md + python3 .github/scripts/issue_format.py /tmp/formatted_body.md # Update issue body with formatted version node - <<'NODE' @@ -718,3 +717,14 @@ jobs: NODE echo "Labels updated: removed format, added formatted" fi + + - name: Release failed format lease + if: (failure() || cancelled()) && steps.check.outputs.should_run == 'true' && steps.check.outputs.phase == 'format' + env: + GH_TOKEN: ${{ github.token }} + ISSUE_NUMBER: ${{ steps.check.outputs.issue_number }} + run: | + set -euo pipefail + # A guard retry may proceed only after the failed format run releases its lease. + gh issue edit "$ISSUE_NUMBER" --remove-label "agents:format" \ + || echo "::warning::could not release failed agents:format lease" diff --git a/config/template-drift-allowlist.txt b/config/template-drift-allowlist.txt index 9a23c65a4..a669a5967 100644 --- a/config/template-drift-allowlist.txt +++ b/config/template-drift-allowlist.txt @@ -113,9 +113,9 @@ reason = Intentional divergence re-baselined 2026-06-30: root and consumer guard [pair.11] main = .github/workflows/agents-issue-optimizer.yml template = templates/consumer-repo/.github/workflows/agents-issue-optimizer.yml -main_sha256 = d99f29c3433fa3430c68338cffe0c9ad7c6e22801303fea1916ddeab4040dc90 -template_sha256 = a4ab5b6f0039c1c20d3fb3133fed3a38902f7943cd46c951cf7695a0d3e41858 -reason = Intentional divergence re-baselined 2026-08-08: root and consumer issue-optimizer workflows keep different auth plumbing/action pin surfaces. Both validate format output before adding agents:formatted; the consumer explicitly sparse-checks out .github/scripts/issue_format.py plus config/scripts/langchain/tools from Workflows, while root runs those files in-tree. Do not align wholesale because that would strip consumer action pins/token setup. +main_sha256 = ccf1cd3ccdba84c54743d2950367308df15c5928b95c1e7400a5d2858b7c1fa5 +template_sha256 = a4d50beb165724ff9266a18390d738105538fe0c13a5eb3a620fb8c9297fe80f +reason = Intentional divergence re-baselined 2026-08-08b: root remains in-tree (scripts/langchain + .github/scripts/issue_format.py); consumer vendors those via Workflows sparse-checkout under workflows-scripts/. Shared behavioral contract this round: concurrency includes inputs.issue_number for workflow_dispatch dedupe, cancel-safe (failure()||cancelled()) agents:format lease release, and fixed issue_dedup Python indentation with visible non-zero exit warnings. Do not align wholesale — that would strip consumer action pins/token setup. [pair.12] main = .github/workflows/agents-keepalive-loop-reporter.yml diff --git a/langsmith-fleet-worker-attempt.json b/langsmith-fleet-worker-attempt.json index 51f4e9cbe..336c7a2e8 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-08T05:39:15.569084Z", + "emitted_at": "2026-08-08T11:32:33.483036Z", "execution_profile": "codex-default", "fallback_models": [ "gpt-5.5" ], "operation_role": "worker", - "pr_number": "2982", + "pr_number": "2981", "requested_model": "gpt-5.6-terra", "runner": "reusable-codex-run", "schema": "langsmith-fleet/v1", diff --git a/templates/consumer-repo/.github/scripts/github-api-with-retry.js b/templates/consumer-repo/.github/scripts/github-api-with-retry.js index 9a13c47e6..99231c2e1 100755 --- a/templates/consumer-repo/.github/scripts/github-api-with-retry.js +++ b/templates/consumer-repo/.github/scripts/github-api-with-retry.js @@ -170,7 +170,7 @@ function logWithCore(core, level, message) { core[level](message); return; } - const logFn = level === 'error' ? console.error : level === 'warning' ? console.warn : console.log; + const logFn = level === 'error' ? console.error : level === 'warning' ? console.warn : console.error; logFn(message); } @@ -428,7 +428,7 @@ async function withRetry(fn, options = {}) { ? 'rate limit' : 'transient error'; - console.log( + console.error( `${retryReason} (attempt ${attempt + 1}/${maxRetries + 1}). ` + `Retrying in ${Math.round(actualDelay / 1000)}s...` ); 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 a4b692e27..3acb8f601 100644 --- a/templates/consumer-repo/.github/workflows/agents-issue-format-guard.yml +++ b/templates/consumer-repo/.github/workflows/agents-issue-format-guard.yml @@ -39,6 +39,13 @@ jobs: with: persist-credentials: false + - name: Setup API client + uses: ./.github/actions/setup-api-client + with: + # This guard only reads issue comments with the workflow token. Do not + # expose the repository-wide secret bundle to the composite action. + github_token: ${{ github.token }} + - name: Resolve issue id: issue env: @@ -132,35 +139,66 @@ jobs: fi fingerprint="$(sha256sum body.md | cut -c1-12)" marker="" - # Only trust exact HTML markers authored by automation bots (not user text). - trusted_marker=false - if gh issue view "$NUMBER" --repo "$GITHUB_REPOSITORY" --json comments \ - --jq '.comments[] | select((.author.login // "") | test("\\[bot\\]$|github-actions"; "i")) | .body' \ - | grep -qF "$marker"; then - trusted_marker=true - fi + # Only trust exact HTML markers authored by github-actions[bot] + # (immutable account id 41898282 when the REST payload includes user.id). + trusted_marker="$(FORMAT_GUARD_MARKER="$marker" node - <<'NODE' + (async () => { + const { Octokit } = require('@octokit/rest'); + const { createTokenAwareRetry } = require('./.github/scripts/github-api-with-retry.js'); + const github = new Octokit({ auth: process.env.GH_TOKEN }); + const core = { info: () => {}, warning: console.warn, debug: () => {} }; + const { paginateWithRetry } = await createTokenAwareRetry({ + github, + core, + env: process.env, + task: 'issue-format-guard', + capabilities: ['issues:read'], + }); + const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/'); + const comments = await paginateWithRetry( + github.rest.issues.listComments, + { owner, repo, issue_number: Number(process.env.NUMBER), per_page: 100 } + ); + const trusted = comments.some((comment) => + comment.user?.login === 'github-actions[bot]' + && (comment.user?.id == null || comment.user.id === 41898282) + && (comment.body || '').includes(process.env.FORMAT_GUARD_MARKER) + ); + process.stdout.write(trusted ? 'true' : 'false'); + })().catch((error) => { + console.error(error); + process.exit(1); + }); + NODE + )" has_format_label=false if jq -e '[.labels[].name] | any(. == "agents:format")' live.json >/dev/null; then has_format_label=true fi - # Dedup completed handoffs, but retry when a prior dispatch/optimizer run - # left agents:format set (marker without a finished optimizer pass). + # The marker is written only after dispatch succeeds. Keep the label as + # the in-flight lease: a repeated guard run must not enqueue another + # optimizer while that lease is still present. If the label was removed, + # retry the dispatch because the earlier handoff no longer owns the work. dispatch=true - if [[ "$trusted_marker" == true && "$has_format_label" != true ]]; then - echo "Identical invalid body was already routed; skipping duplicate dispatch." + if [[ "$trusted_marker" == true && "$has_format_label" == true ]]; then + echo "Identical invalid body is already routed and in flight; skipping duplicate dispatch." dispatch=false - elif [[ "$trusted_marker" == true && "$has_format_label" == true ]]; then - echo "Prior format-guard marker present but agents:format still set — retrying optimizer dispatch." + elif [[ "$trusted_marker" == true ]]; then + echo "Prior format-guard marker present but agents:format is absent — retrying optimizer dispatch." fi if jq -e '[.labels[].name] | any(. == "agents:formatted")' live.json >/dev/null; then gh issue edit "$NUMBER" --repo "$GITHUB_REPOSITORY" --remove-label "agents:formatted" \ || echo "::warning::could not remove agents:formatted" fi - gh issue edit "$NUMBER" --repo "$GITHUB_REPOSITORY" --add-label "agents:format" \ - || echo "::warning::could not apply agents:format (label missing in this repo?)" if [[ "$dispatch" != true ]]; then exit 0 fi + # Acquiring the label is the handoff lease. Do not dispatch without it: + # otherwise a trusted marker alone could repeat the optimizer dispatch. + if ! gh issue edit "$NUMBER" --repo "$GITHUB_REPOSITORY" --add-label "agents:format"; then + echo "::error::could not acquire agents:format lease; refusing optimizer dispatch" + exit 1 + fi # GITHUB_TOKEN label edits do not start issues:labeled workflows; dispatch is explicit. # Persist the completion marker only after a successful workflow_dispatch so a # failed run remains retryable on the next guard pass. diff --git a/templates/consumer-repo/.github/workflows/agents-issue-optimizer.yml b/templates/consumer-repo/.github/workflows/agents-issue-optimizer.yml index d070e9202..ab2f44db1 100644 --- a/templates/consumer-repo/.github/workflows/agents-issue-optimizer.yml +++ b/templates/consumer-repo/.github/workflows/agents-issue-optimizer.yml @@ -18,6 +18,13 @@ on: - apply - format +concurrency: + group: >- + agents-issue-optimizer-${{ + github.repository }}-${{ + github.event.issue.number || inputs.issue_number || github.run_id }} + cancel-in-progress: false + jobs: optimize_issue: runs-on: ubuntu-latest @@ -341,7 +348,7 @@ jobs: }); NODE - python - <<'PY' || true + python - <<'PY' import json import sys sys.path.insert(0, 'workflows-scripts/scripts/langchain') @@ -351,33 +358,37 @@ jobs: issue = json.load(f) with open('/tmp/open_issues.json', encoding='utf-8') as f: open_issues = json.load(f) - with open('/tmp/dedup_comments.json', encoding='utf-8') as f: - comments = json.load(f) + with open('/tmp/dedup_comments.json', encoding='utf-8') as f: + comments = json.load(f) - marker = issue_dedup.SIMILAR_ISSUES_MARKER - for comment in comments or []: - body = (comment or {}).get('body') or '' - if marker in body: - raise SystemExit(0) + marker = issue_dedup.SIMILAR_ISSUES_MARKER + for comment in comments or []: + body = (comment or {}).get('body') or '' + if marker in body: + raise SystemExit(0) # Conservative defaults; can be tuned later. threshold = 0.82 - store = issue_dedup.build_issue_vector_store(open_issues) - if store is None: - raise SystemExit(0) - - title = (issue.get('title') or '').strip() - body = (issue.get('body') or '').strip() - query = f"{title}\n{body}".strip() if body else title - if not query: - raise SystemExit(0) - - matches = issue_dedup.find_similar_issues(store, query, threshold=threshold, k=5) - comment = issue_dedup.format_similar_issues_comment(matches, max_items=5) - if comment: - with open('/tmp/dedup_comment.md', 'w', encoding='utf-8') as out: - out.write(comment) + store = issue_dedup.build_issue_vector_store(open_issues) + if store is None: + raise SystemExit(0) + + title = (issue.get('title') or '').strip() + body = (issue.get('body') or '').strip() + query = f"{title}\n{body}".strip() if body else title + if not query: + raise SystemExit(0) + + matches = issue_dedup.find_similar_issues(store, query, threshold=threshold, k=5) + comment = issue_dedup.format_similar_issues_comment(matches, max_items=5) + if comment: + with open('/tmp/dedup_comment.md', 'w', encoding='utf-8') as out: + out.write(comment) PY + dedup_rc=$? + if [[ $dedup_rc -ne 0 ]]; then + echo "::warning::issue dedup python exited with $dedup_rc; continuing without similar-issues comment" + fi if [[ -f /tmp/dedup_comment.md ]]; then gh issue comment "${ISSUE_NUMBER}" --body-file /tmp/dedup_comment.md || true @@ -559,3 +570,14 @@ jobs: body="${body//RUN_URL_PLACEHOLDER/${RUN_URL}}" gh issue comment "${ISSUE_NUMBER}" --body "$body" || true fi + + - name: Release failed format lease + if: (failure() || cancelled()) && steps.check.outputs.should_run == 'true' && steps.check.outputs.phase == 'format' + env: + GH_TOKEN: ${{ steps.token.outputs.token || github.token }} + ISSUE_NUMBER: ${{ steps.check.outputs.issue_number }} + run: | + set -euo pipefail + # A guard retry may proceed only after the failed format run releases its lease. + gh issue edit "$ISSUE_NUMBER" --remove-label "agents:format" \ + || echo "::warning::could not release failed agents:format lease" diff --git a/tests/workflows/test_agents_issue_optimizer_format_trigger.py b/tests/workflows/test_agents_issue_optimizer_format_trigger.py index 4e4972f64..021aa25fc 100644 --- a/tests/workflows/test_agents_issue_optimizer_format_trigger.py +++ b/tests/workflows/test_agents_issue_optimizer_format_trigger.py @@ -3,6 +3,13 @@ import yaml WORKFLOW_PATH = Path(".github/workflows/agents-issue-optimizer.yml") +CONSUMER_WORKFLOW_PATH = Path( + "templates/consumer-repo/.github/workflows/agents-issue-optimizer.yml" +) +GUARD_PATH = Path(".github/workflows/agents-issue-format-guard.yml") +CONSUMER_GUARD_PATH = Path( + "templates/consumer-repo/.github/workflows/agents-issue-format-guard.yml" +) def _load_workflow() -> dict: @@ -26,20 +33,62 @@ def test_issue_optimizer_checks_for_format_label() -> None: def test_issue_optimizer_validates_format_and_apply_bodies() -> None: text = WORKFLOW_PATH.read_text(encoding="utf-8") - assert "issue_format.py /tmp/formatted_body.md" in text - assert "issue_format.py /tmp/updated_body.md" in text - consumer = Path("templates/consumer-repo/.github/workflows/agents-issue-optimizer.yml") - consumer_text = consumer.read_text(encoding="utf-8") - assert "issue_format.py /tmp/formatted_body.md" in consumer_text - assert "issue_format.py /tmp/updated_body.md" in consumer_text - - -def test_format_guard_retries_incomplete_optimizer_dispatch() -> None: - guard = Path(".github/workflows/agents-issue-format-guard.yml").read_text(encoding="utf-8") - assert "Re-fetch before side effects" in guard - assert "no completion marker written so later runs can retry" in guard - assert 'marker=""' in guard - consumer = Path( - "templates/consumer-repo/.github/workflows/agents-issue-format-guard.yml" - ).read_text(encoding="utf-8") - assert "no completion marker written so later runs can retry" in consumer + consumer_text = CONSUMER_WORKFLOW_PATH.read_text(encoding="utf-8") + assert "python3 .github/scripts/issue_format.py /tmp/formatted_body.md" in text + assert "python3 .github/scripts/issue_format.py /tmp/updated_body.md" in text + # Consumer template vendors issue_format.py from the Workflows sparse checkout. + assert ( + "python workflows-scripts/.github/scripts/issue_format.py /tmp/formatted_body.md" + in consumer_text + ) + assert ( + "python workflows-scripts/.github/scripts/issue_format.py /tmp/updated_body.md" + in consumer_text + ) + + +def test_format_guard_deduplicates_inflight_optimizer_dispatch() -> None: + guard = GUARD_PATH.read_text(encoding="utf-8") + consumer = CONSUMER_GUARD_PATH.read_text(encoding="utf-8") + for text in (guard, consumer): + assert "Re-fetch before side effects" in text + assert "no completion marker written so later runs can retry" in text + assert 'marker=""' in text + assert "comment.user?.login === 'github-actions[bot]'" in text + assert "comment.user?.id == null || comment.user.id === 41898282" in text + assert "createTokenAwareRetry" in text + assert "paginateWithRetry" in text + assert "github.rest.issues.listComments" in text + assert "per_page: 100" in text + 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. + assert text.index("gh workflow run agents-issue-optimizer.yml") < text.index( + 'echo "$marker"' + ) + + +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") + for text in (guard, consumer_guard): + assert "could not acquire agents:format lease" in text + lease_idx = text.index("could not acquire agents:format lease") + dispatch_idx = text.index("gh workflow run agents-issue-optimizer.yml") + assert lease_idx < dispatch_idx + assert "exit 1" in text[lease_idx:dispatch_idx] + # Dispatch must stay inside the success path after lease acquisition. + assert '--add-label "agents:format"' in text[:dispatch_idx] + + for text in ( + WORKFLOW_PATH.read_text(encoding="utf-8"), + CONSUMER_WORKFLOW_PATH.read_text(encoding="utf-8"), + ): + assert "Release failed format lease" in text + assert ( + "(failure() || cancelled()) && steps.check.outputs.should_run == 'true' " + "&& steps.check.outputs.phase == 'format'" + ) in text + assert 'gh issue edit "$ISSUE_NUMBER" --remove-label "agents:format"' in text