Repository navigation
Say which key to unlock when commit signing fails - #28
Conversation
There was a problem hiding this comment.
🟡 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
SigningErrorandsigning_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.
ca29fcd to
75cf63e
Compare
| 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 |
There was a problem hiding this comment.
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.
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.
75cf63e to
1036d87
Compare
| 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)") |
There was a problem hiding this comment.
| 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) |
There was a problem hiding this comment.
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.
| 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") |
There was a problem hiding this comment.
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.


Once the runner stopped waiting on the passphrase prompt,
stack_createcame back with codeunknownand 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.gpgsignwith an SSH key and an agent that hasn't got it, run from somewhere with no terminal).A
git committhat fails on git's signing marker now comes back as a typedSigningErrorcarryinggpg.formatanduser.signingkey, and create and modify turn that into asigning_failederror: thessh-addline 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.