From 8ed65d3c6f5864d65f13f2c57a06fce79f0337fd Mon Sep 17 00:00:00 2001 From: Citation fixture Date: Fri, 23 Oct 2026 09:56:35 -0400 Subject: [PATCH 1/2] Read a fresh bus as empty, type every git failure, and keep test git off the caller's repo test_storage_violations x5 was red on main with SQLITE_CANTOPEN on wire/bus.sqlite3. - Breaking commit 167331380 (SQLite bus integration, via #2620): Bus.observing opened bus.sqlite3 mode=ro, so a root before its first bus write raised CANTOPEN. TypedStore.snapshot already owned "a store before its first write reads as its declared empty schema"; Bus never inherited it. The absent branch moves onto WalStore.committed_or_empty and both inherit it. - Repo.git raised CalledProcessError while goals.CheckedCommit.git raised GitCommandFailed: one git runner copied with drift (IMPL-12). UnitState.read only turns GitCommandFailed into an Unreadable card, so a unit whose worktree vanished crashed the heartbeat. One runner on Repo now (C locale, 60 s, GitCommandFailed, which moves to repo.py); CheckedCommit.git deleted. - StorageViolation.scan_observed skipped Unreadable units, so a gone declared home (the exact case StorageHomeMissing names) never reported once 7dc76e03e made the branch read part of require_read. Storage reads the filesystem, so it scans every held observed unit. - tests/conftest.py strips every GIT_* variable at import. git bisect run from a linked worktree exports GIT_DIR; a fixture's plain `git -C tmp init` then re-initialised the caller's checkout with core.bare=true (the flip seen 3x on 10-23) and registered fixture worktrees on it. Co-Authored-By: Claude Opus 5.5 --- src/agent_comms/bus.py | 7 +++-- src/agent_comms/change_report.py | 12 ++++---- src/agent_comms/goals.py | 40 ++++--------------------- src/agent_comms/repo.py | 41 ++++++++++++++++++++------ src/agent_comms/storage_rules.py | 5 ++-- src/agent_comms/todos.py | 5 ++-- src/agent_comms/typed_table.py | 33 ++++++++++++++------- src/agent_comms/ui_model/unit_state.py | 2 +- tests/conftest.py | 14 +++++++++ tests/test_installed_merge.py | 5 ++-- tests/test_source_revision.py | 2 +- tools/proofs/cn_progress_read_only.py | 3 +- 12 files changed, 97 insertions(+), 72 deletions(-) diff --git a/src/agent_comms/bus.py b/src/agent_comms/bus.py index 510ea53532..c81200a3d9 100644 --- a/src/agent_comms/bus.py +++ b/src/agent_comms/bus.py @@ -460,8 +460,11 @@ def table_schema(cls) -> type[BusSchema]: @contextmanager def observing(self) -> Iterator[sqlite3.Connection]: - """Borrow one existing read-only snapshot, including for an archived bus.""" - with self._attached(lambda: SQLiteFile.observing(self.path)) as db: + """Borrow one read-only snapshot, including for an archived bus; a bus before its + first write reads as its declared empty tables.""" + with self.committed_or_empty( + self.table_schema(), lambda: SQLiteFile.observing(self.path) + ) as db: yield db def backup(self, destination: Path) -> None: diff --git a/src/agent_comms/change_report.py b/src/agent_comms/change_report.py index 16d98aa6c6..cfeaddc53e 100644 --- a/src/agent_comms/change_report.py +++ b/src/agent_comms/change_report.py @@ -26,10 +26,10 @@ SubclassUnion, WireValue, ) -from .goals import CheckedCommit, GitCommandFailed +from .goals import CheckedCommit +from .repo import GitCommandFailed, Repo if TYPE_CHECKING: - from .repo import Repo from .todos import PullRequestLink @@ -55,15 +55,15 @@ def fetch_refspec(self) -> str: ... def lines(self, repository: Path, file: str) -> tuple[str, ...]: if repository not in self._commits: - CheckedCommit.git(str(repository), 'fetch', 'origin', self.fetch_refspec) - self._commits[repository] = CheckedCommit.git( - str(repository), 'rev-parse', '--verify', f'{self.reference}^{{commit}}' + Repo.git(repository, 'fetch', 'origin', self.fetch_refspec) + self._commits[repository] = Repo.git( + repository, 'rev-parse', '--verify', f'{self.reference}^{{commit}}' ).strip() commit = self._commits[repository] root = repository.absolute() path = (root / file).relative_to(root).as_posix() try: - text = CheckedCommit.git(str(repository), 'show', f'{commit}:{path}') + text = Repo.git(repository, 'show', f'{commit}:{path}') return tuple(text.splitlines()) except GitCommandFailed as error: if error.source_path_absent(commit, path): diff --git a/src/agent_comms/goals.py b/src/agent_comms/goals.py index 919de638e6..d31ca01c89 100644 --- a/src/agent_comms/goals.py +++ b/src/agent_comms/goals.py @@ -29,7 +29,7 @@ from agent_comms.goal_turn_permit import GoalTurnPermit, NoLaunchPermit from .turn_goal_permission import ContinuationGoalPermission, GoalPermission, InactiveGoalPermission from .registration_inheritance import RegistrationInheritance -from .repo import ScratchCheckout +from .repo import GitCommandFailed, Repo, ScratchCheckout from .thread_identity import RecordedThreadIncarnation, ThreadIncarnation from .text_digest import TextDigest from .ui_model.goal import GoalDisplay @@ -383,7 +383,7 @@ def recorded_among(self, stored: tuple[CheckedCommit, ...]) -> tuple[ChildResult def at(self, revision: str, repository: str, checkout_base: Path) -> ChildResult: """Read the result on ``revision``, or explicitly run the check in a detached checkout.""" - sha = CheckedCommit.git(repository, "rev-parse", f"{revision}^{{commit}}").strip() + sha = Repo.git(repository, "rev-parse", f"{revision}^{{commit}}").strip() for recorded in self.recorded(sha, repository): return recorded try: @@ -409,28 +409,6 @@ def run_at(self, revision: str, repository: str, checkout_base: Path) -> ChildRe return ChildResult(ChildOutcome.from_returncode(run.returncode), run.stdout) -class GitCommandFailed(RuntimeError): - """A Git operation failure, not malformed order data to aggregate and continue past.""" - - def __init__(self, repository: str, run: subprocess.CompletedProcess[str]): - self.run = run - detail = run.stderr.strip() or f"git failed with exit {run.returncode}" - super().__init__(f"Git in {repository}: {detail}") - - def source_path_absent(self, sha: str, file: str) -> bool: - """Only Git's missing-path result, never a failed fetch or corrupt repository.""" - return self.run.returncode == 128 and self.run.stderr in ( - f"fatal: path '{file}' does not exist in '{sha}'\n", - f"fatal: path '{file}' exists on disk, but not in '{sha}'\n", - ) - - def no_check_ran_on(self, sha: str) -> bool: - """Git's absent-note reply, not a corrupt repository or an invalid revision.""" - return self.run.returncode == 1 and self.run.stderr in ( - "", f"error: no note found for object {sha}.\n", - ) - - @dataclass(frozen=True) class CheckedCommit: """What an order's check printed at a commit. A commit's result never changes, so the git @@ -448,28 +426,20 @@ class CheckedCommit: def result(self) -> ChildResult: return ChildResult(self.outcome, self.stdout.encode()) - @staticmethod - def git(repository: str, *args: str) -> str: - run = subprocess.run(("git", "-C", repository, *args), capture_output=True, text=True, - timeout=60, env=os.environ | {"LC_ALL": "C"}) - if run.returncode: - raise GitCommandFailed(repository, run) - return run.stdout - @classmethod def stored(cls, repository: str, sha: str) -> tuple[CheckedCommit, ...]: """Every check result the commit's note records; a commit no check ran on has none.""" try: - cls.git(repository, *cls.NOTES, "list", sha) + Repo.git(repository, *cls.NOTES, "list", sha) except GitCommandFailed as failure: if failure.no_check_ran_on(sha): return () # No check ran on this commit. raise return tuple(FieldCodec.decode(cls, FieldCodec.parse(line)) for line in - cls.git(repository, *cls.NOTES, "show", sha).splitlines() if line) + Repo.git(repository, *cls.NOTES, "show", sha).splitlines() if line) def store(self, repository: str, sha: str) -> None: - self.git(repository, *self.NOTES, "append", "-m", + Repo.git(repository, *self.NOTES, "append", "-m", FieldCodec.compact(FieldCodec.encode(self)), sha) diff --git a/src/agent_comms/repo.py b/src/agent_comms/repo.py index 936579bb6f..9a38d2c7e4 100644 --- a/src/agent_comms/repo.py +++ b/src/agent_comms/repo.py @@ -2,6 +2,7 @@ from __future__ import annotations +import os import shutil import subprocess from abc import ABC, abstractmethod @@ -31,6 +32,28 @@ def founding(self, commander: str) -> tuple[Repo, ...]: ... def without_commander(self) -> None: ... +class GitCommandFailed(RuntimeError): + """A Git operation failure, not malformed order data to aggregate and continue past.""" + + def __init__(self, repository: str, run: subprocess.CompletedProcess[str]): + self.run = run + detail = run.stderr.strip() or f"git failed with exit {run.returncode}" + super().__init__(f"Git in {repository}: {detail}") + + def source_path_absent(self, sha: str, file: str) -> bool: + """Only Git's missing-path result, never a failed fetch or corrupt repository.""" + return self.run.returncode == 128 and self.run.stderr in ( + f"fatal: path '{file}' does not exist in '{sha}'\n", + f"fatal: path '{file}' exists on disk, but not in '{sha}'\n", + ) + + def no_check_ran_on(self, sha: str) -> bool: + """Git's absent-note reply, not a corrupt repository or an invalid revision.""" + return self.run.returncode == 1 and self.run.stderr in ( + "", f"error: no note found for object {sha}.\n", + ) + + class Repo(BaseModel, WireValue): """A repository the fleet works on: its check checkout, its GitHub name and its source root.""" @@ -95,10 +118,11 @@ def contains(self, sha: str, revision: str = "HEAD") -> bool: result = subprocess.run( ("git", "-C", str(self.checkout), "merge-base", "--is-ancestor", sha, revision), capture_output=True, + text=True, timeout=60, ) if result.returncode not in (0, 1): - result.check_returncode() + raise GitCommandFailed(str(self.checkout), result) return result.returncode == 0 def has_commit(self, sha: str) -> bool: @@ -112,14 +136,13 @@ def has_commit(self, sha: str) -> bool: ) @staticmethod - def git(checkout: Path, *args: str) -> str: - return subprocess.run( - ["git", "-C", str(checkout), *args], - capture_output=True, - text=True, - check=True, - timeout=30, - ).stdout + def git(checkout: Path | str, *args: str) -> str: + """The one Git runner: C-locale output, and every failure a GitCommandFailed.""" + run = subprocess.run(("git", "-C", str(checkout), *args), capture_output=True, text=True, + timeout=60, env=os.environ | {"LC_ALL": "C"}) + if run.returncode: + raise GitCommandFailed(str(checkout), run) + return run.stdout @classmethod def of(cls, checkout: Path) -> Repo: diff --git a/src/agent_comms/storage_rules.py b/src/agent_comms/storage_rules.py index 16d312960b..8d7575734d 100644 --- a/src/agent_comms/storage_rules.py +++ b/src/agent_comms/storage_rules.py @@ -349,9 +349,10 @@ def scan_owner(cls) -> type[StorageViolation]: @classmethod def scan_observed(cls, comms: Comms, commander: str, observed: Sequence[UnitState]) -> tuple[Self, ...]: + # Storage reads the filesystem, not the git source an unreadable card failed on: + # a unit whose declared home is gone is that card and a missing home. held = StandingRule.holders(comms, commander) - return cls.for_units(comms, (unit for state in observed - for unit in state.readable_units() if unit in held)) + return cls.for_units(comms, (state.unit for state in observed if state.unit in held)) @classmethod def for_units(cls, comms: Comms, units: Iterable[ThreadIncarnation]) -> tuple[Self, ...]: diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index 101cc4ae65..be8afd7b1c 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -70,7 +70,8 @@ WireValue, boundary_decoder, ) -from agent_comms.goals import CheckedCommit, GitCommandFailed, OrderCheck, ProblemGoalOrder +from agent_comms.goals import CheckedCommit, OrderCheck, ProblemGoalOrder +from agent_comms.repo import GitCommandFailed from agent_comms.head_review import AcceptedHeadReview from agent_comms.lifecycle_capability import LifecycleCapability, Pending from agent_comms.message_reference import MessageReference @@ -863,7 +864,7 @@ def default_refs(self) -> tuple[str, ...]: return (self.default_reference,) if self.default_reference in self.git_refs else () def git(self, *args: str) -> str: - return CheckedCommit.git(str(self.path), *args) + return Repo.git(self.path, *args) def require_read(self) -> None: self.git("rev-parse", "--git-dir") diff --git a/src/agent_comms/typed_table.py b/src/agent_comms/typed_table.py index 126189d8cf..6956c00dc9 100644 --- a/src/agent_comms/typed_table.py +++ b/src/agent_comms/typed_table.py @@ -2169,6 +2169,26 @@ def retire(*error): lifetime.push(retire) yield connection + @contextmanager + def committed_or_empty( + self, + schema: type[TableSchema], + opening: Callable[[], AbstractContextManager[sqlite3.Connection]], + ) -> Iterator[sqlite3.Connection]: + """Read the committed file, or its declared empty schema before its first write. + + An absent file is established absence: a reader sees every declared table empty + and creates no storage.""" + if self.path.exists(): + with self._attached(opening) as connection: + yield connection + return + with closing(self.writer(":memory:", isolation_level=None)) as connection: + schema.create_schema(connection) + self._configure_reader(connection) + self.begin_snapshot(connection) + yield connection + @contextmanager def _open_writer(self, *, check_same_thread: bool = True) -> Iterator[sqlite3.Connection]: """Open one private writer and apply the file's declared WAL policy.""" @@ -2261,16 +2281,9 @@ def _open_writer(self, *, check_same_thread: bool = True) -> Iterator[sqlite3.Co @contextmanager def snapshot(self) -> Iterator[sqlite3.Connection]: """Read a committed store, or its declared empty schema before its first write.""" - with ExitStack() as lifetime: - if self.path.exists(): - connection = lifetime.enter_context(self._attached( - lambda: self.observing(self.path, StoreBusy()))) - else: - connection = lifetime.enter_context(closing( - self.writer(":memory:", isolation_level=None))) - self.create_schema(connection) - self._configure_reader(connection) - self.begin_snapshot(connection) + with self.committed_or_empty( + type(self), lambda: self.observing(self.path, StoreBusy()) + ) as connection: self.require_schema(connection) yield connection diff --git a/src/agent_comms/ui_model/unit_state.py b/src/agent_comms/ui_model/unit_state.py index e49fa80f82..cbeb3deb4c 100644 --- a/src/agent_comms/ui_model/unit_state.py +++ b/src/agent_comms/ui_model/unit_state.py @@ -18,7 +18,7 @@ from agent_comms.commander.questions import UnansweredUnitAsks from agent_comms.field_codec import MalformedRecord from agent_comms.goal_presentation import GoalSnapshot -from agent_comms.goals import GitCommandFailed +from agent_comms.repo import GitCommandFailed from agent_comms.messages import Message from agent_comms.native_failures import NativePiUnavailable from agent_comms.standing_rules import StandingRule, UndatedStandingRule diff --git a/tests/conftest.py b/tests/conftest.py index 5e6dc8fbe7..66151a797c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -137,6 +137,20 @@ def publish_initial(comms, sender, target, body, type=InfoKind, *, role=Conversa destination=CommandTarget.named(target), origin=AgentOrigin(), ) +def _without_caller_repository() -> None: + """A caller's git selects no repository for any test git call. + + ``git bisect run`` from a linked worktree exports GIT_DIR, and a fixture's + ``git -C tmp init`` then re-initialised the caller's checkout as bare instead of + creating its own. Removed at import, before session start's own git call and before + xdist workers inherit the environment.""" + for name in tuple(name for name in os.environ if name.startswith("GIT_")): + del os.environ[name] + + +_without_caller_repository() + + def pytest_sessionstart(session): """The controller populates this checkout's pinned submodules before any worker runs, as every tree of ours is (Checkout). Collection loads declarations only.""" diff --git a/tests/test_installed_merge.py b/tests/test_installed_merge.py index 54f8a9e8cc..e4a9010ccb 100644 --- a/tests/test_installed_merge.py +++ b/tests/test_installed_merge.py @@ -1,15 +1,14 @@ """Merge observation closes the real stored order, without a completion request.""" from agent_comms.repository_name import RepositoryName -import subprocess import pytest -from agent_comms.repo import Repo +from agent_comms.repo import GitCommandFailed, Repo from test_order_progress import git def test_failed_git_is_not_a_negative_ancestry_result(tmp_path): - with pytest.raises(subprocess.CalledProcessError): + with pytest.raises(GitCommandFailed): Repo(checkout=tmp_path / 'missing', github=RepositoryName.from_wire('test/repo')).contains('a' * 40) diff --git a/tests/test_source_revision.py b/tests/test_source_revision.py index 0575f08740..b78d3714e1 100644 --- a/tests/test_source_revision.py +++ b/tests/test_source_revision.py @@ -9,7 +9,7 @@ from agent_comms.change_report import FileLine, MainRevision, MissingSourceLine, PullRequestHeadRevision from agent_comms.field_codec import MalformedRecord from agent_comms.goal_actions import OrderGoalAction -from agent_comms.goals import GitCommandFailed +from agent_comms.repo import GitCommandFailed from agent_comms.owner_requests import OwnerRequestId from agent_comms.recorded_time import RecordedEventTime from agent_comms.thread_identity import RecordedThreadIncarnation diff --git a/tools/proofs/cn_progress_read_only.py b/tools/proofs/cn_progress_read_only.py index b85aeec744..e4cdab1a52 100644 --- a/tools/proofs/cn_progress_read_only.py +++ b/tools/proofs/cn_progress_read_only.py @@ -10,6 +10,7 @@ from agent_comms.comms import wire from agent_comms.field_codec import FieldCodec from agent_comms.goals import CheckedCommit, CountCheckResult, OrderCheck +from agent_comms.repo import Repo from agent_comms.todos import CorrectCheckTodo, DistanceReadings, PushedProgress, Todo from agent_comms.tools import CommsChangeProblemTool, CommsTodoListTool, TodoList @@ -62,7 +63,7 @@ def verify() -> None: try: assert not board(todo).readings assert not MARKER.exists() - sha = CheckedCommit.git(str(checkout), "rev-parse", "HEAD").strip() + sha = Repo.git(checkout, "rev-parse", "HEAD").strip() assert not check.recorded(sha, str(checkout)) check.at(sha, str(checkout), comms.registry.storage_rule.checkout_base) assert MARKER.read_bytes() == MARKER_CONTENT From 66b3d8104d13fde61b255d31e34b5573cc29948a Mon Sep 17 00:00:00 2001 From: Citation fixture Date: Fri, 23 Oct 2026 10:00:13 -0400 Subject: [PATCH 2/2] Name the git failure GitCommandError and drop the imports the move orphaned debt-src grew LintFindings by one in change_report.py, repo.py and unit_state.py. The exception moved into repo.py kept a name without the Error suffix (N818), change_report.py still imported CheckedCommit it no longer used, and unit_state.py's import block was unsorted. goals.py's os import lost its last user with CheckedCommit.git. Co-Authored-By: Claude Opus 5.5 --- src/agent_comms/change_report.py | 5 ++--- src/agent_comms/goals.py | 5 ++--- src/agent_comms/repo.py | 8 ++++---- src/agent_comms/todos.py | 6 +++--- src/agent_comms/ui_model/unit_state.py | 4 ++-- tests/test_installed_merge.py | 4 ++-- tests/test_source_revision.py | 4 ++-- 7 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/agent_comms/change_report.py b/src/agent_comms/change_report.py index cfeaddc53e..74952a29e9 100644 --- a/src/agent_comms/change_report.py +++ b/src/agent_comms/change_report.py @@ -26,8 +26,7 @@ SubclassUnion, WireValue, ) -from .goals import CheckedCommit -from .repo import GitCommandFailed, Repo +from .repo import GitCommandError, Repo if TYPE_CHECKING: from .todos import PullRequestLink @@ -65,7 +64,7 @@ def lines(self, repository: Path, file: str) -> tuple[str, ...]: try: text = Repo.git(repository, 'show', f'{commit}:{path}') return tuple(text.splitlines()) - except GitCommandFailed as error: + except GitCommandError as error: if error.source_path_absent(commit, path): raise MissingSourceLine( f'Source file {file} does not exist at {commit}.' diff --git a/src/agent_comms/goals.py b/src/agent_comms/goals.py index d31ca01c89..0a74d28476 100644 --- a/src/agent_comms/goals.py +++ b/src/agent_comms/goals.py @@ -4,7 +4,6 @@ from agent_comms.lifecycle_capability import Active -import os import re import shlex import signal @@ -29,7 +28,7 @@ from agent_comms.goal_turn_permit import GoalTurnPermit, NoLaunchPermit from .turn_goal_permission import ContinuationGoalPermission, GoalPermission, InactiveGoalPermission from .registration_inheritance import RegistrationInheritance -from .repo import GitCommandFailed, Repo, ScratchCheckout +from .repo import GitCommandError, Repo, ScratchCheckout from .thread_identity import RecordedThreadIncarnation, ThreadIncarnation from .text_digest import TextDigest from .ui_model.goal import GoalDisplay @@ -431,7 +430,7 @@ def stored(cls, repository: str, sha: str) -> tuple[CheckedCommit, ...]: """Every check result the commit's note records; a commit no check ran on has none.""" try: Repo.git(repository, *cls.NOTES, "list", sha) - except GitCommandFailed as failure: + except GitCommandError as failure: if failure.no_check_ran_on(sha): return () # No check ran on this commit. raise diff --git a/src/agent_comms/repo.py b/src/agent_comms/repo.py index 9a38d2c7e4..f9ddffdfdf 100644 --- a/src/agent_comms/repo.py +++ b/src/agent_comms/repo.py @@ -32,7 +32,7 @@ def founding(self, commander: str) -> tuple[Repo, ...]: ... def without_commander(self) -> None: ... -class GitCommandFailed(RuntimeError): +class GitCommandError(RuntimeError): """A Git operation failure, not malformed order data to aggregate and continue past.""" def __init__(self, repository: str, run: subprocess.CompletedProcess[str]): @@ -122,7 +122,7 @@ def contains(self, sha: str, revision: str = "HEAD") -> bool: timeout=60, ) if result.returncode not in (0, 1): - raise GitCommandFailed(str(self.checkout), result) + raise GitCommandError(str(self.checkout), result) return result.returncode == 0 def has_commit(self, sha: str) -> bool: @@ -137,11 +137,11 @@ def has_commit(self, sha: str) -> bool: @staticmethod def git(checkout: Path | str, *args: str) -> str: - """The one Git runner: C-locale output, and every failure a GitCommandFailed.""" + """The one Git runner: C-locale output, and every failure a GitCommandError.""" run = subprocess.run(("git", "-C", str(checkout), *args), capture_output=True, text=True, timeout=60, env=os.environ | {"LC_ALL": "C"}) if run.returncode: - raise GitCommandFailed(str(checkout), run) + raise GitCommandError(str(checkout), run) return run.stdout @classmethod diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index be8afd7b1c..0f1968202c 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -71,7 +71,7 @@ boundary_decoder, ) from agent_comms.goals import CheckedCommit, OrderCheck, ProblemGoalOrder -from agent_comms.repo import GitCommandFailed +from agent_comms.repo import GitCommandError from agent_comms.head_review import AcceptedHeadReview from agent_comms.lifecycle_capability import LifecycleCapability, Pending from agent_comms.message_reference import MessageReference @@ -1027,7 +1027,7 @@ def of(cls, todo: Todo, registry: Registration, pulls: ProgressRead) -> Self: pulls.checkout(path) if (path / ".git").exists() else MissingCheckout(path)) try: references = checkout.references(storage, owner) - except (OSError, GitCommandFailed, WorkBranchError, subprocess.TimeoutExpired) as failure: + except (OSError, GitCommandError, WorkBranchError, subprocess.TimeoutExpired) as failure: checkout, references = UnreadCheckout(path, str(failure)), () return cls(todo, checkout, pulls, references, tuple(pulls.checkout(path) for path in owned if (path / ".git").exists())) @@ -1096,7 +1096,7 @@ def from_source(cls, source: ProgressSource) -> OrderProgress: try: return next((progress for member in members for progress in member.observations(source)), NoCommitProgress()) - except (OSError, GitCommandFailed, WorkBranchError, subprocess.TimeoutExpired) as unread: + except (OSError, GitCommandError, WorkBranchError, subprocess.TimeoutExpired) as unread: return UnreadProgress(str(unread)) @property diff --git a/src/agent_comms/ui_model/unit_state.py b/src/agent_comms/ui_model/unit_state.py index cbeb3deb4c..a71be593a6 100644 --- a/src/agent_comms/ui_model/unit_state.py +++ b/src/agent_comms/ui_model/unit_state.py @@ -18,9 +18,9 @@ from agent_comms.commander.questions import UnansweredUnitAsks from agent_comms.field_codec import MalformedRecord from agent_comms.goal_presentation import GoalSnapshot -from agent_comms.repo import GitCommandFailed from agent_comms.messages import Message from agent_comms.native_failures import NativePiUnavailable +from agent_comms.repo import GitCommandError from agent_comms.standing_rules import StandingRule, UndatedStandingRule from agent_comms.thread_identity import ThreadIncarnation from agent_comms.thread_status import ThreadStatus @@ -141,7 +141,7 @@ def read(cls, source: UnitObservation) -> UnitState: return next(state for member in cls.worst_first() for state in member.derive(source)) except ( OSError, - GitCommandFailed, + GitCommandError, WorkBranchError, TimeoutExpired, NativePiUnavailable, diff --git a/tests/test_installed_merge.py b/tests/test_installed_merge.py index e4a9010ccb..29d38f7e62 100644 --- a/tests/test_installed_merge.py +++ b/tests/test_installed_merge.py @@ -3,12 +3,12 @@ import pytest -from agent_comms.repo import GitCommandFailed, Repo +from agent_comms.repo import GitCommandError, Repo from test_order_progress import git def test_failed_git_is_not_a_negative_ancestry_result(tmp_path): - with pytest.raises(GitCommandFailed): + with pytest.raises(GitCommandError): Repo(checkout=tmp_path / 'missing', github=RepositoryName.from_wire('test/repo')).contains('a' * 40) diff --git a/tests/test_source_revision.py b/tests/test_source_revision.py index b78d3714e1..5b78986fee 100644 --- a/tests/test_source_revision.py +++ b/tests/test_source_revision.py @@ -9,7 +9,7 @@ from agent_comms.change_report import FileLine, MainRevision, MissingSourceLine, PullRequestHeadRevision from agent_comms.field_codec import MalformedRecord from agent_comms.goal_actions import OrderGoalAction -from agent_comms.repo import GitCommandFailed +from agent_comms.repo import GitCommandError from agent_comms.owner_requests import OwnerRequestId from agent_comms.recorded_time import RecordedEventTime from agent_comms.thread_identity import RecordedThreadIncarnation @@ -49,7 +49,7 @@ def test_pull_request_revision_accepts_branch_only_source_without_checkout(tmp_p assert source.git(source.checkout, 'rev-parse', 'HEAD').strip() == source.main_commit assert source.git(source.checkout, 'rev-parse', 'refs/agent-comms/cited/7').strip() == source.branch_commit # An absent remote PR fails its fetch; it is never silently checked at main. - with pytest.raises(GitCommandFailed, match='remote ref'): + with pytest.raises(GitCommandError, match='remote ref'): with MalformedRecord.refusing('order.owner'): location.require_exists(source.checkout, PullRequestHeadRevision(replace(source.link, number=404)))