From 51701124f6f4aa5b93bf404ba8c7a73a184c46c9 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Mon, 12 Oct 2026 19:03:10 +0000 Subject: [PATCH] Accept a valid handoff note committed more than once Validate last_work_commit against the first commit beneath this attempt's whole run of note-touching commits instead of the commit directly beneath the newest note, so a note committed twice (or with other staged files swept in) no longer downgrades handoff to incomplete. Earlier rounds' note commits are never skipped. Co-Authored-By: Claude Code --- simple_coding_agent/attempt_workspace.py | 14 ++ simple_coding_agent/git_workspace.py | 43 +++- simple_coding_agent/lifecycle.py | 76 ++++++- tests/fakes.py | 19 +- tests/test_attempt_workspace_conformance.py | 98 +++++++++ tests/test_handoff_end_to_end.py | 229 ++++++++++++++++++++ 6 files changed, 457 insertions(+), 22 deletions(-) diff --git a/simple_coding_agent/attempt_workspace.py b/simple_coding_agent/attempt_workspace.py index 54a1c4a..3b59355 100644 --- a/simple_coding_agent/attempt_workspace.py +++ b/simple_coding_agent/attempt_workspace.py @@ -87,6 +87,20 @@ def handoff_note_commit( """ ... + def handoff_note_commits( + self, prepared: PreparedAttempt, issue_number: int, *, attempt_id: str + ) -> tuple[AttemptCommit, ...]: + """Every commit of this attempt touching the issue's handoff note path. + + Newest first, under the same rules as :meth:`handoff_note_commit`: + position-independent, and limited to this attempt (earlier rounds' + note commits are excluded). The newest entry is what + :meth:`handoff_note_commit` returns. Used by handoff-note validation + to skip the unbroken run of this attempt's note commits beneath the + newest one when deriving the expected ``last_work_commit``. + """ + ... + def cleanup( self, *, base_branch: str, prepared: PreparedAttempt, retain_branch: bool ) -> None: diff --git a/simple_coding_agent/git_workspace.py b/simple_coding_agent/git_workspace.py index 9a84eb2..52b79fb 100644 --- a/simple_coding_agent/git_workspace.py +++ b/simple_coding_agent/git_workspace.py @@ -396,17 +396,16 @@ def commit_handoff_note(self, issue_number: int, content: str) -> AttemptCommit: revision = self._git("rev-parse", "HEAD") return AttemptCommit(revision=revision, subject=subject) - def handoff_note_commit( + def handoff_note_commits( self, prepared: PreparedAttempt, issue_number: int, *, attempt_id: str - ) -> AttemptCommit | None: - """Return the newest commit of this attempt touching the handoff note path. - - Position-independent: a seal commit landing on top of a - model-committed note does not hide the note. Notes carried over from - earlier rounds of a continued branch are ignored: only commits - authored at or after the attempt start (``attempt_id`` is the - checkpoint's ``started_at``) count. Author dates survive the rebase - onto the base branch, unlike revisions. An unparseable + ) -> tuple[AttemptCommit, ...]: + """Return every commit of this attempt touching the handoff note path. + + Newest first, under the same rules as :meth:`handoff_note_commit`: + position-independent, and limited to this attempt (notes carried over + from earlier rounds of a continued branch are ignored: only commits + authored at or after the attempt start count). Author dates survive + the rebase onto the base branch, unlike revisions. An unparseable ``attempt_id`` applies no time bound. """ @@ -418,14 +417,34 @@ def handoff_note_commit( "--", handoff_note_relpath(issue_number), ) + found: list[AttemptCommit] = [] for line in output.splitlines(): revision, _, rest = line.partition("\x00") authored, separator, subject = rest.partition("\x00") if not separator or not revision or not authored.isdigit(): raise GitWorkspaceError("Git returned an invalid commit listing") if started is None or int(authored) >= started: - return AttemptCommit(revision=revision, subject=subject) - return None + found.append(AttemptCommit(revision=revision, subject=subject)) + return tuple(found) + + def handoff_note_commit( + self, prepared: PreparedAttempt, issue_number: int, *, attempt_id: str + ) -> AttemptCommit | None: + """Return the newest commit of this attempt touching the handoff note path. + + Position-independent: a seal commit landing on top of a + model-committed note does not hide the note. Notes carried over from + earlier rounds of a continued branch are ignored: only commits + authored at or after the attempt start (``attempt_id`` is the + checkpoint's ``started_at``) count. Author dates survive the rebase + onto the base branch, unlike revisions. An unparseable + ``attempt_id`` applies no time bound. + """ + + commits = self.handoff_note_commits( + prepared, issue_number, attempt_id=attempt_id + ) + return commits[0] if commits else None def cleanup( self, *, base_branch: str, prepared: PreparedAttempt, retain_branch: bool = True diff --git a/simple_coding_agent/lifecycle.py b/simple_coding_agent/lifecycle.py index acf7461..47a1669 100644 --- a/simple_coding_agent/lifecycle.py +++ b/simple_coding_agent/lifecycle.py @@ -3,7 +3,7 @@ from __future__ import annotations import asyncio -from collections.abc import Callable, Mapping, Sequence +from collections.abc import Callable, Collection, Mapping, Sequence from dataclasses import dataclass, replace from datetime import UTC, datetime from enum import StrEnum @@ -661,6 +661,9 @@ def __call__(self, claim: Claim, profile: RepositoryProfile, prepared: PreparedA claim.issue.number, self._workspace.working_directory, prepared.base_revision, + note_commit_revisions=_this_attempt_note_commit_revisions( + self._workspace, prepared, claim.issue.number, attempt_id + ), ) _emit_handoff_note_events( self._event_log, claim.issue.number, validation @@ -1959,6 +1962,12 @@ def _infer_retry_decision( claim.issue.number, self._workspace.working_directory, prepared.base_revision, + note_commit_revisions=_this_attempt_note_commit_revisions( + self._workspace, + prepared, + claim.issue.number, + checkpoint.started_at, + ), ) _emit_handoff_note_events( self._event_log, claim.issue.number, validation @@ -2022,6 +2031,12 @@ def _infer_release_outcome( claim.issue.number, self._workspace.working_directory, prepared.base_revision, + note_commit_revisions=_this_attempt_note_commit_revisions( + self._workspace, + prepared, + claim.issue.number, + checkpoint.started_at, + ), ) except Exception: validation = None @@ -3321,6 +3336,9 @@ def _check_operator_note( issue_number, working_directory, prepared.base_revision, + note_commit_revisions=_this_attempt_note_commit_revisions( + self._workspace, prepared, issue_number, attempt_id + ), ) except Exception as error: return False, f"the handoff note could not be validated ({_exception_detail(error)})" @@ -3617,6 +3635,12 @@ def _reconcile_startup(self) -> AttemptOutcome | None: claim.issue.number, self._workspace.working_directory, prepared.base_revision, + note_commit_revisions=_this_attempt_note_commit_revisions( + self._workspace, + prepared, + claim.issue.number, + checkpoint.started_at, + ), ) _emit_handoff_note_events( self._event_log, claim.issue.number, validation @@ -4864,12 +4888,36 @@ class _HandoffNoteValidation: started_at_warning: str | None = None +def _this_attempt_note_commit_revisions( + workspace: AttemptWorkspace, + prepared: PreparedAttempt, + issue_number: int, + attempt_id: str, +) -> frozenset[str]: + """Revisions of this attempt's note-touching commits for note validation. + + An unreadable note history fails closed to just the newest note commit, + which is exactly the pre-fix strictness: validation then expects the + commit directly beneath the note. + """ + + try: + commits = workspace.handoff_note_commits( + prepared, issue_number, attempt_id=attempt_id + ) + except Exception: + return frozenset() + return frozenset(commit.revision for commit in commits) + + def _validate_handoff_note( note_commit: AttemptCommit | None, commits: Sequence[AttemptCommit], issue_number: int, working_directory: Path, base_revision: str, + *, + note_commit_revisions: Collection[str] | None = None, ) -> _HandoffNoteValidation: """Confirm a handoff has a trustworthy, committed note before it may publish. @@ -4937,15 +4985,33 @@ def _validate_handoff_note( last_work_commit = fields.get("last_work_commit", "") # The note describes the work preserved beneath it: the attempt commit # directly under the note in newest-first order, or the base revision - # when the note is the oldest attempt commit. + # when the note is the oldest attempt commit. When the model committed + # the note more than once, the commits beneath the newest note commit + # are earlier note commits of this same attempt, not work: skip the + # unbroken run of this attempt's note-touching commits below the note + # (whatever other files they also carry) and expect the first commit + # beneath that run. Note commits from earlier rounds of a continued + # branch are never in ``note_commit_revisions``, so a new round that + # names the previous round's note commit (HEAD at injection) still + # validates. + if note_commit_revisions is None: + note_revisions = {note_commit.revision} + else: + note_revisions = set(note_commit_revisions) | {note_commit.revision} note_index = next( (index for index, commit in enumerate(commits) if commit.revision == note_commit.revision), None, ) - if note_index is not None and note_index + 1 < len(commits): - expected_last_work_commit = commits[note_index + 1].revision - else: + if note_index is None: expected_last_work_commit = base_revision + else: + cursor = note_index + 1 + while cursor < len(commits) and commits[cursor].revision in note_revisions: + cursor += 1 + if cursor < len(commits): + expected_last_work_commit = commits[cursor].revision + else: + expected_last_work_commit = base_revision if ( len(last_work_commit) < 7 or not expected_last_work_commit.startswith(last_work_commit) diff --git a/tests/fakes.py b/tests/fakes.py index 744eb2c..869023f 100644 --- a/tests/fakes.py +++ b/tests/fakes.py @@ -267,14 +267,23 @@ def prior_round_commit(self, subject: str, files: tuple[str, ...]) -> AttemptCom return self._record_commit(subject, "", frozenset(files), clear_dirty=False, prior_round=True) + def handoff_note_commits( + self, prepared: PreparedAttempt, issue_number: int, *, attempt_id: str + ) -> tuple[AttemptCommit, ...]: + relpath = handoff_note_relpath(issue_number) + return tuple( + AttemptCommit(revision=record.revision, subject=record.subject) + for record in reversed(self._commits) + if relpath in record.files and not record.prior_round + ) + def handoff_note_commit( self, prepared: PreparedAttempt, issue_number: int, *, attempt_id: str ) -> AttemptCommit | None: - relpath = handoff_note_relpath(issue_number) - for record in reversed(self._commits): - if relpath in record.files and not record.prior_round: - return AttemptCommit(revision=record.revision, subject=record.subject) - return None + commits = self.handoff_note_commits( + prepared, issue_number, attempt_id=attempt_id + ) + return commits[0] if commits else None def cleanup( self, *, base_branch: str, prepared: PreparedAttempt, retain_branch: bool diff --git a/tests/test_attempt_workspace_conformance.py b/tests/test_attempt_workspace_conformance.py index 8b3022c..efb807e 100644 --- a/tests/test_attempt_workspace_conformance.py +++ b/tests/test_attempt_workspace_conformance.py @@ -335,6 +335,104 @@ def test_handoff_note_commit_ignores_a_note_from_an_earlier_round( assert found.revision == note.revision +def test_handoff_note_commits_returns_every_note_commit_newest_first( + attempt: SimpleNamespace, +) -> None: + first = attempt.workspace.commit_handoff_note(24, "# Handoff note: issue #24\n\none\n") + model_commit(attempt, "work.txt", "work", "Model work") + second = attempt.workspace.commit_handoff_note(24, "# Handoff note: issue #24\n\ntwo\n") + + commits = attempt.workspace.handoff_note_commits( + attempt.prepared, 24, attempt_id=attempt.attempt_id + ) + + assert [entry.revision for entry in commits] == [second.revision, first.revision] + assert ( + attempt.workspace.handoff_note_commit( + attempt.prepared, 24, attempt_id=attempt.attempt_id + ) + == commits[0] + ) + + +def test_handoff_note_commits_includes_a_note_commit_with_other_files( + attempt: SimpleNamespace, +) -> None: + """A note commit that also carries work still touches the note path (#34).""" + + if attempt.kind == "git": + note_path = attempt.root / ".agent" / "handoff" / "24.md" + note_path.parent.mkdir(parents=True, exist_ok=True) + note_path.write_text("# Handoff note: issue #24\n") + (attempt.root / "work.txt").write_text("work") + git(attempt.root, "add", ".agent/handoff/24.md", "work.txt") + git(attempt.root, "commit", "-m", "Handoff note: issue #24") + note = attempt.workspace.handoff_note_commit( + attempt.prepared, 24, attempt_id=attempt.attempt_id + ) + assert note is not None + else: + attempt.workspace.make_dirty(".agent/handoff/24.md", "# Handoff note: issue #24\n") + attempt.workspace.make_dirty("work.txt", "work") + note = attempt.workspace.model_commit( + "Handoff note: issue #24", + files=(".agent/handoff/24.md", "work.txt"), + ) + + commits = attempt.workspace.handoff_note_commits( + attempt.prepared, 24, attempt_id=attempt.attempt_id + ) + + assert [entry.revision for entry in commits] == [note.revision] + + +def test_handoff_note_commits_excludes_notes_from_an_earlier_round( + attempt: SimpleNamespace, +) -> None: + prior_round_commit( + attempt, ".agent/handoff/24.md", "# Handoff note: issue #24\n", "Handoff note: issue #24" + ) + note = attempt.workspace.commit_handoff_note(24, "# Handoff note: issue #24\n\nnew\n") + + commits = attempt.workspace.handoff_note_commits( + attempt.prepared, 24, attempt_id=attempt.attempt_id + ) + + assert [entry.revision for entry in commits] == [note.revision] + + +def test_handoff_note_commits_survives_a_seal_commit_on_top( + attempt: SimpleNamespace, +) -> None: + note = attempt.workspace.commit_handoff_note(24, "# Handoff note: issue #24\n") + dirty_file(attempt, "work.txt", "uncommitted edits") + seal_commit = attempt.workspace.seal( + issue_number=24, + attempt_id="2026-09-29T00:00:00Z", + status=ModelExecutionStatus.HANDOFF_REQUESTED, + ) + assert seal_commit is not None + + commits = attempt.workspace.handoff_note_commits( + attempt.prepared, 24, attempt_id=attempt.attempt_id + ) + + assert [entry.revision for entry in commits] == [note.revision] + + +def test_handoff_note_commits_is_empty_without_a_note( + attempt: SimpleNamespace, +) -> None: + model_commit(attempt, "work.txt", "work", "Model work") + + assert ( + attempt.workspace.handoff_note_commits( + attempt.prepared, 24, attempt_id=attempt.attempt_id + ) + == () + ) + + def test_cleanup_on_a_dirty_tree_stays_on_the_attempt_branch( attempt: SimpleNamespace, ) -> None: diff --git a/tests/test_handoff_end_to_end.py b/tests/test_handoff_end_to_end.py index 3d2037c..7965d8a 100644 --- a/tests/test_handoff_end_to_end.py +++ b/tests/test_handoff_end_to_end.py @@ -675,6 +675,235 @@ def do_handoff(working_directory: Path) -> None: assert "last_work_commit" in comment.body +# --- Scenario 11d: a handoff note committed more than once -------------------- +# +# The model sometimes commits the note, then edits and commits it again. The +# commit beneath the newest note commit is then the previous note commit, not +# work -- but the note still names the last preserved work commit beneath the +# whole run of this attempt's note commits, so it must still publish as a +# ``handoff``. + + +def test_handoff_note_committed_twice_names_the_work_commit(tmp_path: Path) -> None: + """The #33 shape: work commit W, note-only commit, note-only commit.""" + + remote, _ = repository_with_main(tmp_path) + github = FakeGitHub(issue(24, body="Rewrite the parser.", labels=frozenset({"ready-for-agent"}))) + clone_dir = tmp_path / "clone" + + def do_handoff(working_directory: Path) -> None: + write_and_commit(working_directory, "parser.py", "half done", "Half-finish the parser") + work = git(working_directory, "rev-parse", "HEAD") + write_handoff_note(working_directory, 24, "Halfway done.\n", last_work_commit=work) + write_handoff_note( + working_directory, 24, "Halfway done, with more detail.\n", last_work_commit=work + ) + + executor = FakeModelExecutor(actions=[do_handoff], handoff=True) + lifecycle = build_lifecycle(remote, clone_dir, tmp_path / "data", github, executor) + + result = lifecycle.run_once() + + assert result.outcome is AttemptOutcome.HANDOFF + assert github.pull_requests == {} + assert github.added_labels == [("issue-24", "round-finished")] + [comment] = [c for c in github.comments if "Agent Attempt Result: handoff" in c.body] + assert "Halfway done, with more detail." in comment.body + + +def test_handoff_note_with_work_swept_into_first_note_names_the_base(tmp_path: Path) -> None: + """The #34 shape: the first note commit also carries another staged file. + + The swept-in change does not count as "last work": it stays preserved on + the branch for the continuation to read, while the note names the base + revision beneath the run of this attempt's note commits. + """ + + remote, _ = repository_with_main(tmp_path) + github = FakeGitHub(issue(24, body="Rewrite the parser.", labels=frozenset({"ready-for-agent"}))) + clone_dir = tmp_path / "clone" + + def do_handoff(working_directory: Path) -> None: + base = git(working_directory, "rev-parse", "HEAD") + (working_directory / "moved.txt").write_text("moved work") + note_dir = working_directory / ".agent" / "handoff" + note_dir.mkdir(parents=True, exist_ok=True) + (note_dir / "24.md").write_text(_note_text(24, base, "Halfway done.\n")) + git(working_directory, "add", ".agent/handoff/24.md", "moved.txt") + git(working_directory, "commit", "-m", "Handoff note: issue #24") + write_handoff_note( + working_directory, 24, "Halfway done, with more detail.\n", last_work_commit=base + ) + + executor = FakeModelExecutor(actions=[do_handoff], handoff=True) + lifecycle = build_lifecycle(remote, clone_dir, tmp_path / "data", github, executor) + + result = lifecycle.run_once() + + assert result.outcome is AttemptOutcome.HANDOFF + assert github.added_labels == [("issue-24", "round-finished")] + # The swept-in change is still preserved on the branch. + assert git(clone_dir, "show", "agent/issue-24:moved.txt") == "moved work" + + +def test_handoff_note_with_work_swept_into_first_note_names_prior_work(tmp_path: Path) -> None: + """The #34 shape on top of a work commit: the note names that work commit.""" + + remote, _ = repository_with_main(tmp_path) + github = FakeGitHub(issue(24, body="Rewrite the parser.", labels=frozenset({"ready-for-agent"}))) + clone_dir = tmp_path / "clone" + + def do_handoff(working_directory: Path) -> None: + write_and_commit(working_directory, "parser.py", "half done", "Half-finish the parser") + work = git(working_directory, "rev-parse", "HEAD") + (working_directory / "moved.txt").write_text("moved work") + note_dir = working_directory / ".agent" / "handoff" + note_dir.mkdir(parents=True, exist_ok=True) + (note_dir / "24.md").write_text(_note_text(24, work, "Halfway done.\n")) + git(working_directory, "add", ".agent/handoff/24.md", "moved.txt") + git(working_directory, "commit", "-m", "Handoff note: issue #24") + write_handoff_note( + working_directory, 24, "Halfway done, with more detail.\n", last_work_commit=work + ) + + executor = FakeModelExecutor(actions=[do_handoff], handoff=True) + lifecycle = build_lifecycle(remote, clone_dir, tmp_path / "data", github, executor) + + result = lifecycle.run_once() + + assert result.outcome is AttemptOutcome.HANDOFF + assert github.added_labels == [("issue-24", "round-finished")] + assert git(clone_dir, "show", "agent/issue-24:moved.txt") == "moved work" + + +def test_handoff_note_naming_an_earlier_note_commit_is_rejected(tmp_path: Path) -> None: + """A note naming a commit inside the skipped run is still a mismatch.""" + + remote, _ = repository_with_main(tmp_path) + github = FakeGitHub(issue(24, body="Rewrite the parser.", labels=frozenset({"ready-for-agent"}))) + clone_dir = tmp_path / "clone" + + def do_handoff(working_directory: Path) -> None: + write_and_commit(working_directory, "parser.py", "half done", "Half-finish the parser") + work = git(working_directory, "rev-parse", "HEAD") + write_handoff_note(working_directory, 24, "Halfway done.\n", last_work_commit=work) + first_note = git(working_directory, "rev-parse", "HEAD") + write_handoff_note( + working_directory, 24, "Halfway done, with more detail.\n", last_work_commit=first_note + ) + + executor = FakeModelExecutor(actions=[do_handoff], handoff=True) + lifecycle = build_lifecycle(remote, clone_dir, tmp_path / "data", github, executor) + + result = lifecycle.run_once() + + assert result.outcome is AttemptOutcome.INCOMPLETE + assert github.added_labels == [] + [comment] = github.comments + assert "## Agent Attempt Result: incomplete" in comment.body + assert "last_work_commit" in comment.body + + +# --- Scenario 11f: this round's note on a continued branch -------------------- +# +# An earlier round's note commit can sit directly under this round's notes. +# When this round made no commit before the note, the note names that earlier +# note commit (HEAD at injection) -- and it must stay accepted, as before. +# Earlier rounds' note commits are never skipped. + + +def test_continuation_round_note_names_the_prior_round_note_commit(tmp_path: Path) -> None: + remote, _ = repository_with_main(tmp_path) + _publish_prior_round_with_note(tmp_path, remote, 24) + github = FakeGitHub(issue(24, body="Rewrite the parser.", labels=frozenset({"ready-for-agent"}))) + clone_dir = tmp_path / "clone" + + def do_handoff(working_directory: Path) -> None: + head = git(working_directory, "rev-parse", "HEAD") + write_handoff_note(working_directory, 24, "Continuing prior work.\n", last_work_commit=head) + + executor = FakeModelExecutor(actions=[do_handoff], handoff=True) + lifecycle = build_lifecycle(remote, clone_dir, tmp_path / "data", github, executor) + + result = lifecycle.run_once() + + assert result.outcome is AttemptOutcome.HANDOFF + assert github.added_labels == [("issue-24", "round-finished")] + + +def test_continuation_round_note_committed_twice_names_the_prior_note_commit( + tmp_path: Path, +) -> None: + remote, _ = repository_with_main(tmp_path) + _publish_prior_round_with_note(tmp_path, remote, 24) + github = FakeGitHub(issue(24, body="Rewrite the parser.", labels=frozenset({"ready-for-agent"}))) + clone_dir = tmp_path / "clone" + + def do_handoff(working_directory: Path) -> None: + head = git(working_directory, "rev-parse", "HEAD") + write_handoff_note(working_directory, 24, "Continuing prior work.\n", last_work_commit=head) + write_handoff_note( + working_directory, 24, "Continuing prior work, with detail.\n", last_work_commit=head + ) + + executor = FakeModelExecutor(actions=[do_handoff], handoff=True) + lifecycle = build_lifecycle(remote, clone_dir, tmp_path / "data", github, executor) + + result = lifecycle.run_once() + + assert result.outcome is AttemptOutcome.HANDOFF + assert github.added_labels == [("issue-24", "round-finished")] + + +def _note_text(issue_number: int, last_work_commit: str, body: str) -> str: + """Render a valid handoff note body the way ``write_handoff_note`` does.""" + + return ( + f"# Handoff note: issue #{issue_number}\n\n" + f"- issue: {issue_number}\n" + "- started_at: 2026-09-22T00:00:00Z\n" + "- reason: token_soft_threshold\n" + f"- last_work_commit: {last_work_commit}\n\n" + f"{body}" + ) + + +def _publish_prior_round_with_note(tmp_path: Path, remote: Path, issue_number: int) -> str: + """Push an earlier round's work commit plus valid note commit, both backdated. + + The backdated author dates keep them out of the next round's attempt, the + way ``prior_round_commit`` does in the workspace conformance suite (the + ``--date`` flag sets the author date, which is what note-commit filtering + reads). Returns the prior note revision. + """ + + published = tmp_path / f"prior-{issue_number}" + git(tmp_path, "clone", str(remote), str(published)) + git(published, "config", "user.name", "Test User") + git(published, "config", "user.email", "test@example.com") + git(published, "checkout", "-b", f"agent/issue-{issue_number}") + write_and_commit(published, "parser.py", "prior work", "Prior round work") + # Backdate the work commit's author date as well, so the whole prior + # round reads as an earlier round. + git(published, "commit", "--amend", "--no-edit", "--date=2020-01-01T00:00:00Z") + prior_work = git(published, "rev-parse", "HEAD") + note_dir = published / ".agent" / "handoff" + note_dir.mkdir(parents=True, exist_ok=True) + (note_dir / f"{issue_number}.md").write_text( + _note_text(issue_number, prior_work, "Prior round halfway done.\n") + ) + git(published, "add", f".agent/handoff/{issue_number}.md") + git( + published, + "commit", + "--date=2020-01-01T00:00:00Z", + "-m", + f"Handoff note: issue #{issue_number}", + ) + git(published, "push", "-u", "origin", f"agent/issue-{issue_number}") + return git(published, "rev-parse", "HEAD") + + def test_handoff_holds_for_the_operator_when_the_seal_commit_fails(tmp_path: Path) -> None: """Apply the evidence-preserving hold when the seal commit itself fails.