Skip to content

Say which key to unlock when commit signing fails - #28

Merged
DomBlack merged 1 commit into
detach-children-from-terminalfrom
explain-signing-failures
Oct 9, 2026
Merged

DomBlack merged 1 commit into
detach-children-from-terminalfrom
explain-signing-failures

Conversation

@DomBlack

@DomBlack DomBlack commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

Once the runner stopped waiting on the passphrase prompt, stack_create came back with code unknown and git's stderr, with "incorrect passphrase supplied to decrypt private key" buried after "failed to write commit object". Nothing said the key just needed loading into ssh-agent, which is the whole fix for the common case (commit.gpgsign with an SSH key and an agent that hasn't got it, run from somewhere with no terminal).

A git commit that fails on git's signing marker now comes back as a typed SigningError carrying gpg.format and user.signingkey, and create and modify turn that into a signing_failed error: the ssh-add line for that key (or the gpg check when the format is openpgp), the option of turning signing off for the repository, and git's output as the detail. Nothing is committed and the staged changes stay staged. The MCP instructions and docs list the new code.

@DomBlack
DomBlack added this pull request to stack #31 October 9, 2026 16:30
@DomBlack
DomBlack requested a balanced review from Copilot October 9, 2026 16:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Detection currently misclassifies object-write failures, misses passthrough failures, and provides invalid guidance for some keys and X.509 signing.

4 open findings
What changed in this PR

Adds actionable commit-signing failures across Git, app, and MCP layers.

Changes:

  • Introduces SigningError and signing_failed.
  • Adds SSH/OpenPGP recovery guidance.
  • Adds signing-failure tests and MCP documentation.
File Description
pkg/​stack/​errors.go Adds the error kind and code.
pkg/​mcp/​instructions.go Documents MCP recovery behavior.
pkg/​git/​commit.go Detects and describes signing failures.
pkg/​git/​commit_test.go Tests locked SSH-key handling.
pkg/​app/​staging.go Maps failures to actionable stack errors.
pkg/​app/​mutate_test.go Tests recovery guidance.
pkg/​app/​modify.go Applies signing-error mapping to modify.
pkg/​app/​create.go Applies signing-error mapping to create.
docs/​mcp.md Documents the new error code.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread pkg/app/staging.go Outdated
Comment thread pkg/app/staging.go
Comment thread pkg/git/commit.go Outdated
Comment thread pkg/git/commit.go Outdated
@DomBlack
DomBlack force-pushed the explain-signing-failures branch from ca29fcd to 75cf63e Compare October 9, 2026 17:00
Copilot AI balanced review requested due to automatic review settings October 9, 2026 17:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Comment thread pkg/app/staging.go Outdated
Comment on lines +122 to +124
if !strings.HasPrefix(se.Key, "key::") {
if q, ok := shell.Path(strings.TrimSuffix(se.Key, ".pub")); ok {
step = "load the key into ssh-agent: ssh-add " + q

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.

Done. When user.signingkey names a key file, git-stack now resolves it against the repository root (that's where git runs the signer) before it goes in the ssh-add line, and a value that isn't a file there is treated as a literal key and gets the generic ssh-add wording rather than a made up path. Tests for both.

Comment thread pkg/git/commit.go
Once the runner stopped waiting on the passphrase prompt, stack_create came back with code "unknown" and git's stderr, with "incorrect passphrase supplied to decrypt private key" buried after "failed to write commit object". Nothing said the key just needed loading into ssh-agent, which is the whole fix for the common case (commit.gpgsign with an SSH key and an agent that hasn't got it, run from somewhere with no terminal).

A git commit that fails on git's signing marker now comes back as a typed SigningError carrying gpg.format and user.signingkey, and create and modify turn that into a signing_failed error: the ssh-add line for that key (or the gpg check when the format is openpgp), the option of turning signing off for the repository, and git's output as the detail. Nothing is committed and the staged changes stay staged. The MCP instructions and docs list the new code.
Copilot AI balanced review requested due to automatic review settings October 9, 2026 17:17
@DomBlack
DomBlack force-pushed the explain-signing-failures branch from 75cf63e to 1036d87 Compare October 9, 2026 17:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Create retries are unsafe after partial branch creation, and some recovery output is inaccurate or unsafely formatted.

4 open findings
1 resolved since last review

🧠 Review effort: Balanced

Comment thread pkg/app/create.go
sha, err := a.d.Git.Commit(ctx, repo, git.CommitOptions{Message: message, NoVerify: o.NoVerify})
if err != nil {
return res, editorError(err, "pass -m <message> (the branch was created; commit with git commit)")
return res, commitError(err, "pass -m <message> (the branch was created; commit with git commit)")

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.

This is what #29 in the same stack is for: create undoes the branch and its registration when the commit fails, so running it again is the right recovery there. On its own this PR only improves the message, and I'd rather not grow a second recovery path that #29 then deletes.

Comment thread pkg/app/staging.go
Comment on lines +124 to +133
step := "load the signing key into ssh-agent with ssh-add"
if filepath.IsAbs(se.Key) || strings.HasPrefix(se.Key, "~") {
if q, ok := shell.Path(strings.TrimSuffix(se.Key, ".pub")); ok {
step = "load the key into ssh-agent: ssh-add " + q
} else {
step = "load the private key next to " + se.Key + " into ssh-agent with ssh-add"
}
}
e = stack.Newf(stack.KindSigningFailed, "commit signing failed: the SSH key %s could not be used (is it unlocked in ssh-agent?)", se.Key).
WithSteps(step)

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.

A user.signingkey with a control character in it is a config I'm happy to print badly; the Detail already carries whatever git printed, which includes the same path. Leaving this one.

Comment thread pkg/app/staging.go
WithSteps("check that the signer (gpg.x509.program, gpgsm by default) can sign without a prompt")
default:
e = stack.New(stack.KindSigningFailed, "commit signing failed: gpg could not sign the commit (its agent may need a terminal to ask for the passphrase)").
WithSteps("check that gpg can sign without a prompt: echo test | gpg --batch --clearsign")

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.

Fair that it's not definitive, but it's a hint, not a check we act on; the real test is running the command again, which the next steps say. Leaving the wording.

@DomBlack
DomBlack merged commit 1bf1b2f into main Oct 9, 2026
6 checks passed
@DomBlack
DomBlack deleted the explain-signing-failures branch October 9, 2026 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants