From 92361cbeaca0b0ea3321750676375ad7dc2fa291 Mon Sep 17 00:00:00 2001 From: ATECHPCS Date: Mon, 27 Jul 2026 11:20:11 -0400 Subject: [PATCH] fix(gitstore): return real git object ids instead of hex-of-hex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `porcelain.commit()` and `repo.refs[...]` hand back object ids as a 40-character hex string that is already encoded to bytes. Calling `.hex()` on that encodes the ASCII a second time, so every id GitStore produced or displayed was double-encoded: auto_commit() -> '62623234' git log --abbrev=8 -> 'bb244606' The module is self-consistently wrong, so `/dream-log` and `/dream-restore` work as long as the id came from nanobot itself. What does not work is crossing the boundary: ids in logs and commit output match nothing in `git log`, and an id copied from `git log` cannot be resolved: _resolve_sha(own id) -> b'bb244606d780...' _resolve_sha(real git id) -> None Use `.decode()` at the four sites that consume dulwich object ids. Nothing persists an id — callers either display it or resolve it live — so there is no stored state in the old format. Adds two regression tests: the id returned by `auto_commit` must equal `git log --abbrev=8`, and a real git id must resolve through `_resolve_sha`. Co-Authored-By: Claude --- nanobot/utils/gitstore.py | 11 +++++++---- tests/utils/test_gitstore.py | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/nanobot/utils/gitstore.py b/nanobot/utils/gitstore.py index 01ab1fb9a..f09325077 100644 --- a/nanobot/utils/gitstore.py +++ b/nanobot/utils/gitstore.py @@ -176,7 +176,10 @@ class GitStore: ) if cast(object, sha_bytes) is None: return None - sha = sha_bytes.hex()[:8] + # porcelain.commit returns the id as a 40-char hex string that is + # already encoded to bytes; .hex() would encode those ASCII bytes + # again and produce an id no git command can resolve. + sha = sha_bytes.decode()[:8] logger.debug("Git auto-commit: {} ({})", sha, message) return sha except Exception as exc: @@ -200,7 +203,7 @@ class GitStore: return None while sha: - if sha.hex().startswith(short_sha): + if sha.decode().startswith(short_sha): return sha commit_obj = repo[sha] if commit_obj.type_name != b"commit": @@ -280,7 +283,7 @@ class GitStore: msg = commit.message.decode("utf-8", errors="replace").strip() if message_prefix is None or msg.startswith(message_prefix): entries.append(CommitInfo( - sha=sha.hex()[:8], + sha=sha.decode()[:8], message=msg, timestamp=ts, )) @@ -484,7 +487,7 @@ class GitStore: with Repo(str(self._workspace)) as repo: commit = cast("Commit", repo[full_sha]) parent = commit.parents[0] if commit.parents else None - diff = self.diff_commits(parent.hex()[:8], c.sha) if parent else "" + diff = self.diff_commits(parent.decode()[:8], c.sha) if parent else "" return c, diff return None except Exception as exc: diff --git a/tests/utils/test_gitstore.py b/tests/utils/test_gitstore.py index c5b7c16ff..14a2d7fd6 100644 --- a/tests/utils/test_gitstore.py +++ b/tests/utils/test_gitstore.py @@ -310,3 +310,25 @@ class TestNestedRepoProtection: assert result is False assert not (workspace / ".git").exists() + + +class TestCommitIdEncoding: + """Commit ids must be usable with git, not hex-of-hex.""" + + def test_auto_commit_returns_the_real_short_sha(self, git, tmp_path): + (tmp_path / "MEMORY.md").write_text("- a fact\n", encoding="utf-8") + sha = git.auto_commit("memory update") + expected = subprocess.run( + ["git", "-C", str(tmp_path), "log", "-1", "--format=%h", "--abbrev=8"], + capture_output=True, text=True, check=True, + ).stdout.strip() + assert sha == expected + + def test_a_real_git_sha_resolves(self, git, tmp_path): + (tmp_path / "MEMORY.md").write_text("- a fact\n", encoding="utf-8") + git.auto_commit("memory update") + real = subprocess.run( + ["git", "-C", str(tmp_path), "log", "-1", "--format=%h", "--abbrev=8"], + capture_output=True, text=True, check=True, + ).stdout.strip() + assert git._resolve_sha(real) is not None