diff --git a/.github/scripts/issue_format.py b/.github/scripts/issue_format.py index ed14a338ee..ce7affdb55 100644 --- a/.github/scripts/issue_format.py +++ b/.github/scripts/issue_format.py @@ -103,7 +103,10 @@ def _headings(body: str) -> list[tuple[str, int, int]]: def _find(body: str, aliases: tuple[str, ...]) -> int | None: for text, idx, _ in _headings(body): - if any(text == alias or text.startswith(f"{alias} (") for alias in aliases): + if any( + text == alias or text.startswith((f"{alias} (", f"{alias} -", f"{alias} /")) + for alias in aliases + ): return idx return None diff --git a/.github/workflows/agents-issue-format-guard.yml b/.github/workflows/agents-issue-format-guard.yml index 5d9efd0b4d..89472ed245 100644 --- a/.github/workflows/agents-issue-format-guard.yml +++ b/.github/workflows/agents-issue-format-guard.yml @@ -61,7 +61,7 @@ jobs: id: validate if: steps.issue.outputs.exempt != 'true' run: | - set +e + set -euo pipefail report_file="$(mktemp)" error_file="$(mktemp)" trap 'rm -f "$report_file" "$error_file"' EXIT @@ -70,6 +70,7 @@ jobs: echo "rc=skip" >> "$GITHUB_OUTPUT" exit 0 fi + set +e python3 .github/scripts/issue_format.py body.md > "$report_file" 2> "$error_file" rc=$? set -e diff --git a/langsmith-fleet-worker-attempt.json b/langsmith-fleet-worker-attempt.json index dd1ed3e8ad..7c6e3b2bf8 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-07T17:46:15.035406Z", + "emitted_at": "2026-08-07T22:39:42.671782Z", "execution_profile": "codex-default", "fallback_models": [ "gpt-5.5" ], "operation_role": "worker", - "pr_number": "2973", + "pr_number": "2976", "requested_model": "gpt-5.6-terra", "runner": "reusable-codex-run", "schema": "langsmith-fleet/v1", diff --git a/templates/consumer-repo/.github/scripts/issue_format.py b/templates/consumer-repo/.github/scripts/issue_format.py index ed14a338ee..ce7affdb55 100644 --- a/templates/consumer-repo/.github/scripts/issue_format.py +++ b/templates/consumer-repo/.github/scripts/issue_format.py @@ -103,7 +103,10 @@ def _headings(body: str) -> list[tuple[str, int, int]]: def _find(body: str, aliases: tuple[str, ...]) -> int | None: for text, idx, _ in _headings(body): - if any(text == alias or text.startswith(f"{alias} (") for alias in aliases): + if any( + text == alias or text.startswith((f"{alias} (", f"{alias} -", f"{alias} /")) + for alias in aliases + ): return idx return None 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 5d9efd0b4d..89472ed245 100644 --- a/templates/consumer-repo/.github/workflows/agents-issue-format-guard.yml +++ b/templates/consumer-repo/.github/workflows/agents-issue-format-guard.yml @@ -61,7 +61,7 @@ jobs: id: validate if: steps.issue.outputs.exempt != 'true' run: | - set +e + set -euo pipefail report_file="$(mktemp)" error_file="$(mktemp)" trap 'rm -f "$report_file" "$error_file"' EXIT @@ -70,6 +70,7 @@ jobs: echo "rc=skip" >> "$GITHUB_OUTPUT" exit 0 fi + set +e python3 .github/scripts/issue_format.py body.md > "$report_file" 2> "$error_file" rc=$? set -e diff --git a/tests/scripts/test_issue_format.py b/tests/scripts/test_issue_format.py index 25fea0f0f7..5098814f7f 100644 --- a/tests/scripts/test_issue_format.py +++ b/tests/scripts/test_issue_format.py @@ -12,8 +12,7 @@ ) -def _validator(): - path = Path(".github/scripts/issue_format.py") +def _validator(path: Path = Path(".github/scripts/issue_format.py")): spec = spec_from_file_location("issue_format", path) assert spec and spec.loader module = module_from_spec(spec) @@ -51,6 +50,26 @@ def test_heading_with_trailing_qualifier_matches_required_section() -> None: assert report.ok +@pytest.mark.parametrize("separator", ["-", "/"]) +@pytest.mark.parametrize( + "validator_path", + [ + Path(".github/scripts/issue_format.py"), + Path("templates/consumer-repo/.github/scripts/issue_format.py"), + ], +) +def test_heading_with_spaced_trailing_qualifier_matches_required_section( + separator: str, validator_path: Path +) -> None: + validator = _validator(validator_path) + report = validator.validate( + VALID_CONTEXT + + f"## Tasks {separator} in order\n- [ ] Implement it\n\n" + + f"## Acceptance Criteria {separator} all must hold\n- pytest tests/test_x.py passes\n" + ) + assert report.ok + + def test_checkbox_and_subjective_errors_are_non_conforming() -> None: validator = _validator() report = validator.validate(