Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions .github/workflows/reusable-codex-run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1174,6 +1174,7 @@ jobs:
python - <<'PY'
import json
import os
import tempfile
from datetime import datetime, timezone

payload = {
Expand All @@ -1193,17 +1194,28 @@ jobs:
"pr_number": os.environ.get("PR_NUMBER", ""),
"emitted_at": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
}
with open("langsmith-fleet-worker-attempt.json", "w", encoding="utf-8") as fh:
# Write OUTSIDE the checkout. This file exists only to feed the upload-artifact
# step below, and a staging file in the working tree is a liability: the commit
# step later in this job runs `git add -A` and then subtracts a hand-curated
# exclusion list, so any artifact not on that list is committed onto whatever PR
# is open. This one was not, so it landed in six consumer repos and collided
# there with the copy the previous merge had left on main. RUNNER_TEMP is wiped
# between jobs and is never part of the repository, which removes the class
# rather than adding entry N+1 to the list.
out_dir = os.environ.get("RUNNER_TEMP") or tempfile.gettempdir()
out_path = os.path.join(out_dir, "langsmith-fleet-worker-attempt.json")
with open(out_path, "w", encoding="utf-8") as fh:
json.dump(payload, fh, indent=2, sort_keys=True)
fh.write("\n")
print(f"worker model attempt artifact written to {out_path}")
PY

- name: Upload worker model attempt artifact
if: always()
uses: actions/upload-artifact@v7
with:
name: langsmith-fleet-v1-worker-attempt-${{ inputs.pr_number || github.run_id }}
path: langsmith-fleet-worker-attempt.json
path: ${{ runner.temp }}/langsmith-fleet-worker-attempt.json
retention-days: 30

- name: Analyze Codex session
Expand Down Expand Up @@ -1651,8 +1663,8 @@ jobs:
git reset HEAD -- \
codex-output*.md \
codex-prompt*.md \
codex-session-*.jsonl \
codex-analysis-*.json \
codex-session*.jsonl \
codex-analysis*.json \
claude-output*.md \
claude-prompt*.md \
claude-session*.log \
Expand Down
14 changes: 13 additions & 1 deletion scripts/sync_status_file_ignores.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@
"workloop-state.md",
# Test/coverage artifacts
"coverage.xml",
# Per-run agent execution telemetry (HIGH conflict risk). reusable-codex-run.yml rewrites
# this into the checkout root every agent round to stage its upload-artifact step; while
# tracked, codex-autofix committed the diff onto whatever PR was open and the next PR
# collided with main's copy. Patterns, not the literal name, because the file is named after
# the role recorded; bounded by extension so langsmith_*.py sources stay committable.
# ROOT-ANCHORED, and that leading slash is load-bearing. Unanchored, a gitignore pattern
# matches at EVERY depth, so `langsmith-fleet*.json` also swallowed this repo's own tracked
# docs/contracts/schemas/langsmith-fleet-v1.schema.json -- verified with check-ignore, not
# inferred. Same near-miss as the node_modules work: the debris lands in the checkout ROOT,
# so that is the only place the pattern should reach.
"/langsmith-fleet*.json",
"/langsmith-fleet*.ndjson",
# Wrong package manager artifacts (defense-in-depth)
"Pipfile.lock",
"poetry.lock",
Expand All @@ -85,7 +97,7 @@
# Sync from: stranske/Workflows templates/consumer-repo/.gitignore
# Validate: python scripts/sync_status_file_ignores.py --check
# =============================================================================
# Template-Version: 5
# Template-Version: 6
# BEGIN WORKFLOWS STATUS FILES
"""

Expand Down
18 changes: 17 additions & 1 deletion templates/consumer-repo/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ cython_debug/
# Sync from: stranske/Workflows templates/consumer-repo/.gitignore
# Validate: python scripts/sync_status_file_ignores.py --check
# =============================================================================
# Template-Version: 5
# Template-Version: 6
# BEGIN WORKFLOWS STATUS FILES

# Agent working files (HIGH conflict risk)
Expand Down Expand Up @@ -188,6 +188,22 @@ workloop-state.md
# Test/coverage artifacts
coverage.xml

# Per-run agent execution telemetry (HIGH conflict risk)
# reusable-codex-run.yml writes langsmith-fleet-worker-attempt.json into the checkout
# root on every agent round, only to stage the actions/upload-artifact step that
# follows it. While tracked, every round produced a diff that codex-autofix committed
# onto whatever PR was open, so the next PR collided with the copy the last merge left
# on main. A PATTERN, not the literal: the file is named after the ROLE recorded, so a
# verifier or evaluator attempt arrives as a sibling. Bounded to the two extensions the
# langsmith-fleet/v1 schema emits, so langsmith_*.py sources and langsmith-fleet-*.md
# docs stay committable. ROOT-ANCHORED: unanchored, a pattern matches at every depth, and
# `langsmith-fleet*.json` also swallowed Workflows' own tracked
# docs/contracts/schemas/langsmith-fleet-v1.schema.json (verified with check-ignore). The
# debris only ever lands in the checkout root, so that is all the pattern should reach --
# the same anchoring lesson the node_modules entry below records.
/langsmith-fleet*.json
/langsmith-fleet*.ndjson

# Wrong package manager artifacts (defense-in-depth)
Pipfile.lock
poetry.lock
Expand Down
44 changes: 44 additions & 0 deletions tests/scripts/test_sync_status_file_ignores.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,50 @@ def test_template_has_version_and_anchors() -> None:
assert fallback_version == template_version


def test_langsmith_patterns_are_root_anchored_and_spare_the_tracked_schema(
tmp_path: Path,
) -> None:
"""The langsmith-fleet debris rules must reach the checkout ROOT and nothing deeper.

Unanchored, a gitignore pattern matches at every depth, so `langsmith-fleet*.json`
also matched this repo's own tracked `docs/contracts/schemas/langsmith-fleet-v1.schema.json`
-- silently making a load-bearing contract file untrackable. That is the same near-miss
the vendored-node_modules exception records, so it gets the same kind of guard: asserted
against real `git check-ignore`, not against the pattern string. Drop either leading
slash in FALLBACK_PATTERNS and the schema assertion below fails.
"""
subprocess = pytest.importorskip("subprocess")
run = subprocess.run

run(["git", "init", "-q", str(tmp_path)], check=True)
(tmp_path / ".gitignore").write_text(_full_gitignore_content(), encoding="utf-8")

debris = "langsmith-fleet-worker-attempt.json"
schema = "docs/contracts/schemas/langsmith-fleet-v1.schema.json"
for rel in (debris, schema):
target = tmp_path / rel
target.parent.mkdir(parents=True, exist_ok=True)
target.touch()

def ignored(rel: str) -> bool:
return run(["git", "check-ignore", "-q", rel], cwd=tmp_path, check=False).returncode == 0

# The per-run artifact lands in the root, and that is the only place it lands.
assert ignored(debris), f"{debris} must be ignored -- it is per-run CI debris"
# The schema is committed source and must survive the pattern.
assert not ignored(
schema
), f"{schema} must stay trackable; the langsmith-fleet rules lost their root anchor"

# And the anchoring is visible in the canonical list itself, so a future edit that
# re-broadens it has to delete a leading slash on purpose.
langsmith = [
pattern for pattern in sync_status_file_ignores.CANONICAL_PATTERNS if "langsmith" in pattern
]
assert langsmith, "the langsmith-fleet debris patterns went missing from the canonical list"
assert all(pattern.startswith("/") for pattern in langsmith), langsmith


def test_load_template_patterns_requires_version_marker(
monkeypatch: pytest.MonkeyPatch,
) -> None:
Expand Down
Loading
Loading