Skip to content

Add support for signing checkpoint commits#960

Open
pjbgf wants to merge 7 commits intomainfrom
pjbgf/sign-commits
Open

Add support for signing checkpoint commits#960
pjbgf wants to merge 7 commits intomainfrom
pjbgf/sign-commits

Conversation

@pjbgf
Copy link
Copy Markdown
Member

@pjbgf pjbgf commented Apr 15, 2026

Entire will now sign checkpoint commits (shadow branch, metadata branch) using the same key configured for regular git commits. Signing is best-effort: if the signer is unavailable or fails, the commit is created unsigned and a warning is logged to .entire/logs/.

Users may opt-out from checkpoint commit signing. This does not affect signing of regular user commits.

Add to .entire/settings.json (shared with the team) or .entire/settings.local.json (personal, gitignored):

{
  "sign_checkpoint_commits": false
}

Fixes #311.


Note

Medium Risk
Touches commit creation paths for checkpoints, trails, merges, and migration by adding optional signing and new go-git plugin dependencies; failures are best-effort but could change commit object bytes and introduce environment-specific behavior (gpg/ssh-agent).

Overview
Enables best-effort signing for git commits created by Entire (checkpoint v1/v2 commits, shadow branch commits, trail commits, merge/cherry-pick commits, and migration task-metadata splices) by routing commit creation through CreateCommit(ctx, ...) and applying SignCommitBestEffort before storing the commit.

Registers a go-git ObjectSigner plugin at startup that reads global/system git config (commit.gpgsign, user.signingkey, gpg.format) and can use ssh-agent when available; adds a new setting sign_checkpoint_commits (default enabled) to allow opting out, plus documentation and dependency updates to support the signer plugins.

Reviewed by Cursor Bugbot for commit 9deb0f1. Configure here.

Copilot AI review requested due to automatic review settings April 15, 2026 13:39
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Duplicated commit-creation logic with independent signing
    • Replaced the duplicated createCommit body in strategy/common.go with a single-line delegation to checkpoint.CreateCommit, eliminating the duplicated signing call and ensuring future changes stay consistent.
  • ✅ Fixed: SSH agent connection opened but never closed
    • Wrapped connectSSHAgent with sync.Once so the SSH agent socket connection is opened at most once per process and reused on subsequent calls, preventing file descriptor accumulation.

Create PR

Or push these changes by commenting:

@cursor push f25329b356
Preview (f25329b356)
diff --git a/cmd/entire/cli/strategy/common.go b/cmd/entire/cli/strategy/common.go
--- a/cmd/entire/cli/strategy/common.go
+++ b/cmd/entire/cli/strategy/common.go
@@ -1439,40 +1439,9 @@
 //
 // See push_common.go and session_test.go for usage examples.
 
-// createCommit creates a commit object
+// createCommit creates a commit object by delegating to checkpoint.CreateCommit.
 func createCommit(ctx context.Context, repo *git.Repository, treeHash, parentHash plumbing.Hash, message, authorName, authorEmail string) (plumbing.Hash, error) { //nolint:unparam // already present in codebase
-	now := time.Now()
-	sig := object.Signature{
-		Name:  authorName,
-		Email: authorEmail,
-		When:  now,
-	}
-
-	commit := &object.Commit{
-		TreeHash:  treeHash,
-		Author:    sig,
-		Committer: sig,
-		Message:   message,
-	}
-
-	// Add parent if not a new branch
-	if parentHash != plumbing.ZeroHash {
-		commit.ParentHashes = []plumbing.Hash{parentHash}
-	}
-
-	checkpoint.SignCommitBestEffort(ctx, commit)
-
-	obj := repo.Storer.NewEncodedObject()
-	if err := commit.Encode(obj); err != nil {
-		return plumbing.ZeroHash, fmt.Errorf("failed to encode commit: %w", err)
-	}
-
-	hash, err := repo.Storer.SetEncodedObject(obj)
-	if err != nil {
-		return plumbing.ZeroHash, fmt.Errorf("failed to store commit: %w", err)
-	}
-
-	return hash, nil
+	return checkpoint.CreateCommit(ctx, repo, treeHash, parentHash, message, authorName, authorEmail)
 }
 
 // getSessionDescriptionFromTree reads the first line of prompt.txt from a git tree.

diff --git a/cmd/entire/main.go b/cmd/entire/main.go
--- a/cmd/entire/main.go
+++ b/cmd/entire/main.go
@@ -9,6 +9,7 @@
 	"os/signal"
 	"runtime"
 	"strings"
+	"sync"
 	"syscall"
 
 	"github.com/entireio/cli/cmd/entire/cli"
@@ -92,20 +93,30 @@
 	})
 }
 
+var (
+	sshAgentOnce   sync.Once
+	sshAgentClient agent.Agent
+)
+
 // connectSSHAgent connects to the SSH agent via SSH_AUTH_SOCK.
+// The connection is established at most once per process and reused.
 // Returns nil if the agent is unavailable.
 func connectSSHAgent() agent.Agent {
-	sock := os.Getenv("SSH_AUTH_SOCK")
-	if sock == "" {
-		return nil
-	}
+	sshAgentOnce.Do(func() {
+		sock := os.Getenv("SSH_AUTH_SOCK")
+		if sock == "" {
+			return
+		}
 
-	conn, err := net.Dial("unix", sock)
-	if err != nil {
-		return nil
-	}
+		conn, err := net.Dial("unix", sock)
+		if err != nil {
+			return
+		}
 
-	return agent.NewClient(conn)
+		sshAgentClient = agent.NewClient(conn)
+	})
+
+	return sshAgentClient
 }
 
 var scopeName = map[config.Scope]string{

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 9deb0f1. Configure here.

Comment thread cmd/entire/cli/strategy/common.go Outdated
Comment thread cmd/entire/main.go Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds best-effort signing for Entire’s checkpoint-related commits (shadow/metadata/trails refs, merge/cherry-pick commits, v2 checkpoint refs), using the user’s Git signing configuration when available, with an opt-out setting in .entire/settings*.json.

Changes:

  • Introduces checkpoint.SignCommitBestEffort and wires it into all checkpoint/strategy commit-creation paths.
  • Adds sign_checkpoint_commits setting (default on) and documents checkpoint signing behavior/requirements.
  • Updates go-git v6 + adds go-git objectsigner plugin dependencies to support signing.

Reviewed changes

Copilot reviewed 27 out of 28 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
go.mod Updates go-git v6 pseudo-version and adds objectsigner/crypto dependencies needed for signing.
go.sum Updates dependency checksums corresponding to new go-git + signing dependencies.
docs/architecture/checkpoint-signing.md New architecture doc describing requirements, formats, and best-effort semantics.
cmd/entire/main.go Registers an ObjectSigner plugin (intended to enable signing based on Git config).
cmd/entire/cli/trail/store.go Threads context.Context into commit creation for trails branch updates.
cmd/entire/cli/strategy/push_v2.go Passes context into merge-commit creation so signing can run.
cmd/entire/cli/strategy/push_common.go Adds signing hook to merge commit creation and updates signature to accept context.
cmd/entire/cli/strategy/push_v2_test.go Updates tests for new CreateCommit(ctx, ...) signature.
cmd/entire/cli/strategy/metadata_reconcile.go Signs cherry-pick commits best-effort and threads context through.
cmd/entire/cli/strategy/metadata_reconcile_test.go Updates tests for new CreateCommit(ctx, ...) signature.
cmd/entire/cli/strategy/common.go Updates local helper commit creation to accept context and apply signing.
cmd/entire/cli/strategy/manual_commit_test.go Updates helper commit creation calls to include context.
cmd/entire/cli/strategy/clean_test.go Updates helper commit creation calls to include context.
cmd/entire/cli/settings/settings.go Adds sign_checkpoint_commits setting + helpers for default-on behavior.
cmd/entire/cli/settings/settings_test.go Extends settings validation test to accept/read sign_checkpoint_commits.
cmd/entire/cli/migrate.go Threads context into v2 task metadata splice commits to support signing.
cmd/entire/cli/migrate_test.go Updates migration tests for new context-threaded commit helpers.
cmd/entire/cli/integration_test/explain_test.go Updates integration test helper commit creation signature.
cmd/entire/cli/doctor_test.go Updates doctor tests for new CreateCommit(ctx, ...) signature.
cmd/entire/cli/checkpoint/v2_store.go Threads context into v2 ref update commit creation.
cmd/entire/cli/checkpoint/v2_store_test.go Updates v2 store tests for the updated updateRef(ctx, ...) signature.
cmd/entire/cli/checkpoint/v2_read_test.go Updates v2 read test to call updateRef(ctx, ...).
cmd/entire/cli/checkpoint/v2_generation.go Threads context into v2 generation rotation commit creation.
cmd/entire/cli/checkpoint/v2_generation_test.go Updates generation tests for new CreateCommit(ctx, ...) signature.
cmd/entire/cli/checkpoint/v2_committed.go Threads context into committed v2 ref updates to support signing.
cmd/entire/cli/checkpoint/temporary.go Threads context into temporary checkpoint commit creation.
cmd/entire/cli/checkpoint/committed_reader_resolve_test.go Updates resolve test helper commit creation signature.
cmd/entire/cli/checkpoint/committed.go Adds SignCommitBestEffort, updates CreateCommit signature to accept context, and applies signing.

Comment thread cmd/entire/cli/checkpoint/committed.go
Comment thread cmd/entire/cli/checkpoint/committed.go Outdated
Comment thread cmd/entire/cli/checkpoint/committed.go
Comment thread cmd/entire/main.go Outdated
Comment thread cmd/entire/main.go Outdated
Comment on lines +85 to +88
signer, err := auto.FromConfig(cfg)
if err != nil {
fmt.Fprintf(os.Stderr, "warning: failed to create object signer: %v\n", err)
return nil
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

These warnings are printed directly to os.Stderr, but the PR description says signing failures should be logged to .entire/logs/ (and the repo generally uses cli/logging for operational messages). Consider switching these to logging.Warn (passing a context) so they go to the standard log sink, and avoid user-facing stderr noise during normal runs.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This was intentional to avoid having to wire in the logging this early in the execution process. We can revisit this if others believe is the right thing to do.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wouldn't it be a better place to put this logic here? ->

PersistentPostRun: func(cmd *cobra.Command, _ []string) {
root command but in a PersistentPreRun where we can init the logger and this and .. maybe adding it into the context ? wdyt ?

@pjbgf pjbgf force-pushed the pjbgf/sign-commits branch from deb8840 to e0c4617 Compare April 15, 2026 14:52
@pjbgf pjbgf marked this pull request as ready for review April 15, 2026 14:54
@pjbgf pjbgf requested a review from a team as a code owner April 15, 2026 14:54
@pjbgf pjbgf force-pushed the pjbgf/sign-commits branch 2 times, most recently from 0c16a2e to c6af756 Compare April 16, 2026 12:58
pjbgf added 7 commits April 16, 2026 16:06
Register an ObjectSigner plugin in main.go that loads system and global
git config to obtain gpg.format and user.signingKey. In CreateCommit,
sign the commit when a signer is available via signCommitBestEffort.

Signed-off-by: Paulo Gomes <paulo@entire.io>
Assisted-by: Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Paulo Gomes <paulo@entire.io>
Export SignCommitBestEffort and add signing to createCommit,
createMergeCommitCommon, and createCherryPickCommit so that all
internally created commits are signed when a signer is registered.

Signed-off-by: Paulo Gomes <paulo@entire.io>
Assisted-by: Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 10e057177578
Only create an ObjectSigner when commit.gpgSign is true, so users
without signing enabled see no warnings. Connect to the SSH agent
via SSH_AUTH_SOCK when available, enabling key::, .pub, and
agent-based SSH signing.

Signed-off-by: Paulo Gomes <paulo@entire.io>
Assisted-by: Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 87d00b47af3f
…ommits setting

SignCommitBestEffort now logs errors via the logging package instead of
returning them, so users with hardware security keys or other signers
requiring physical presence won't have checkpoint commits blocked.

Add sign_checkpoint_commits setting (default: true) to let users opt out
of signing checkpoint commits entirely via .entire/settings.json.

Thread context.Context through CreateCommit, createCommit, updateRef,
createMergeCommitCommon, createCherryPickCommit, and related helpers so
that signing warnings flow to .entire/logs/ like all other operational
messages.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Paulo Gomes <paulo@entire.io>
Entire-Checkpoint: 855bb9174e57
Signed-off-by: Paulo Gomes <paulo@entire.io>
Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Paulo Gomes <paulo@entire.io>
Entire-Checkpoint: e4d4d2037f61
Entire-Checkpoint: a70d97c363c8
Entire-Checkpoint: 983194b9963d
@pjbgf pjbgf force-pushed the pjbgf/sign-commits branch from c6af756 to 3fdf9a4 Compare April 16, 2026 15:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Commits don't respect commit.gpgsign git config (unsigned commits)

3 participants