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
10 changes: 10 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"hooks": {
"SessionStart": [
{"hooks": [{"type": "command", "command": "uphold hook claude-code", "async": true}]}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Background checks overwrite edits

The new asynchronous SessionStart hook runs a test that rewrites the real toolchain.toml. If rustc is installed, test_a_version_the_host_cannot_satisfy_is_refused saves the file, changes its Rust requirement, runs deps.sh, then blindly restores the saved bytes. An agent editing that file during the check loses its changes when the test restores the old contents.

Run this test against a temporary copy before enabling the suite in the background.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6e36fa7. The test now copies scripts/deps.sh into a temporary directory beside the edited manifest and runs it there. The tree's toolchain.toml is never written; its checksum is unchanged across a run. No other Python test writes into the tree: the release-script tests use a sandbox.

],
"Stop": [
{"hooks": [{"type": "command", "command": "uphold hook claude-code", "timeout": 600}]}
]
}
}
13 changes: 13 additions & 0 deletions policy/principles.toml
Original file line number Diff line number Diff line change
Expand Up @@ -638,3 +638,16 @@ files.exclude = ["**/tests/**", "**/test/**", "**/*_test.go", "policy/**", "READ

[override.no-pinned-versioned-fetch]
files.exclude = ["**/tests/**", "**/test/**", "**/*_test.go", "policy/**", "README.md", "third_party/**", "vendor/**", ".github/workflows/release.yml"]

# What an agent's turn may not end red. `uphold hook claude-code`, configured
# for SessionStart and Stop in .claude/settings.json, runs these at session
# start and again when the agent stops, and blocks once on a command that was
# green when the session started. The reconcile and the Python suite, and not
# `cargo test`: these run at the end of every turn, and a gate that adds a
# minute or more to each one is a gate people turn off. The Rust suite stays at
# pre-push and in CI.
[stop]
run = [
["uphold", "check"],
["uv", "run", "--no-project", "python", "-m", "unittest", "discover", "-s", "tests"],
]
21 changes: 13 additions & 8 deletions tests/test_toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import shutil
import subprocess
import tempfile
import tomllib
import unittest
from pathlib import Path
Expand Down Expand Up @@ -60,10 +61,10 @@ class Preflight(unittest.TestCase):
reason "not installed" and would pass while proving nothing.
"""

def _check(self) -> subprocess.CompletedProcess[str]:
def _check(self, root: Path) -> subprocess.CompletedProcess[str]:
return subprocess.run(
["./scripts/deps.sh", "check"],
cwd=REPO,
cwd=root,
capture_output=True,
text=True,
check=False,
Expand All @@ -75,12 +76,16 @@ def test_a_version_the_host_cannot_satisfy_is_refused(self) -> None:
impossible = manifest.replace(f'want = "{want}"', 'want = ">=999.0"', 1)
self.assertNotEqual(manifest, impossible, "the rustc `want` line moved")

original = (REPO / "toolchain.toml").read_bytes()
try:
(REPO / "toolchain.toml").write_text(impossible, encoding="utf-8")
done = self._check()
finally:
(REPO / "toolchain.toml").write_bytes(original)
# In a copy, never the tree: this suite runs as the [stop] gate while
# an agent works, and writing the real toolchain.toml and restoring it
# afterwards would revert any edit made to it in between. deps.sh reads
# only the toolchain.toml beside its own scripts/ directory.
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
(root / "scripts").mkdir()
shutil.copy2(REPO / "scripts" / "deps.sh", root / "scripts" / "deps.sh")
(root / "toolchain.toml").write_text(impossible, encoding="utf-8")
done = self._check(root)

report = done.stdout + done.stderr
self.assertNotEqual(done.returncode, 0, report)
Expand Down
Loading