🐋🔧:fix SSH-format commit signing & verification in devcontainer - #1778
Merged
Conversation
#1775 replaced postCreateCommand+postStartCommand with postCreateCommand alone. postCreateCommand only ever runs once, at container build time, but the SSH agent VS Code forwards is a fresh, per-session socket -- so nothing was left to re-materialize gpg.format=ssh's signing-key file on later attaches. VS Code forwards the running agent automatically but never copies key files in, so a user.signingkey path copied verbatim from the host's gitconfig (e.g. ~/.ssh/id_ed25519.pub) pointed at a file that had never existed in the container, and `git commit -S` failed with "Couldn't load public key ...: No such file or directory". post-start.sh now runs on every attach and writes the forwarded agent's public key to user.signingkey's path whenever the agent is holding exactly one identity. It can't sign with the wrong key even if the agent's identity turns out to be unrelated: the actual signature still goes through the agent by fingerprint, so a mismatched file just makes ssh-keygen report no matching identity instead of mis-signing silently. Also drops the "To sign commits: git config --global commit.gpgsign true" hint from post-create.sh's closing banner -- it was often already true (git config copied from the host) and was never the actual blocker, so it just gave false reassurance. post-start.sh's own output now reports accurate, per-attach signing status instead. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The doc still described the container architecture #1775 removed: a custom base image with its own sshd on a forwarded port 2222, a `vscode` user, and a manual `RemoteForward` SSH tunnel (`host gpgtunnel`) to reach a GPG agent extra socket -- mostly written for Git Bash/Gpg4Win on Windows. None of that exists anymore; following it now would send someone looking for a port and a user that are no longer there. Replaced it with what's actually true of the current devcontainer: VS Code forwards a running SSH or GPG agent automatically with no devcontainer.json config needed, but never copies key material in, which is why gpg.format=ssh needs post-start.sh's help (see the previous commit) and gpg.format=openpgp doesn't. Covers setup for both signing formats, notes that GitHub Desktop's "Automatically sign commits" preference is the gpg.format=ssh path, and adds a troubleshooting section keyed to post-start.sh's own output. Also updates project-terms.txt: adds the git-config tokens the rewrite now uses (gpgsign, openpgp, signingkey) and drops updatestartuptty and psusan, whose only uses were in the content just removed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previous commit's rewrite asserted that GitHub Desktop has an "Automatically sign commits" preference which configures gpg.format=ssh for you. It has no such setting. Desktop has no commit-signing UI and no key of its own; it shells out to Git and inherits whatever git config already says, so configuring Git is the whole job whether you commit from Desktop or a terminal. Says so explicitly now, and adds the thing that actually misleads people here: a "Verified" badge on GitHub proves nothing about local signing. Commits made or squash-merged through github.com are signed server-side with GitHub's web-flow key (B5690EEEBB952194) and render identically to locally signed ones. Every signature in this repository's history is that key -- there is not one locally signed commit -- which is easy to mistake for working local signing. Adds a `git cat-file commit HEAD` check so the question can be settled by looking rather than assuming. Also notes that step 2 has to run on the host: the container gets its own copy of ~/.gitconfig at build time, so running those commands inside the container configures only the container and leaves the Mac untouched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A `commit.gpgsign = false` in .git/config beats `true` in the global config and turns signing off for every tool touching the clone, while every `--global` check keeps reporting that signing is enabled. Nothing in a diff shows it either, since .git/config isn't version controlled -- so it is worth ruling out early rather than after re-auditing the global config. Worth documenting rather than only fixing: the working tree is bind-mounted from the host, so .git is shared, and a `--local` setting applied from inside the container silently applies to the host clone as well. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
post-start.sh wrote the forwarded agent's public key to user.signingkey's existing path, which VS Code copies verbatim from the host's gitconfig -- typically /Users/<you>/.ssh/id_ed25519.pub on a Mac, /home/<you>/... on Linux. Both live under a directory this container's non-root `node` user has no write access to (/Users and /home are root-owned, 755), so `mkdir -p` on that literal path failed with EACCES every time, on every host OS, not just macOS. `set -uo pipefail` without `-e` let the failure pass silently, so the script still printed "Commit signing ready" while nothing had been written -- confirmed live: git commit -S failed with "Couldn't load public key /Users/derek/.ssh/id_ed25519.pub: No such file or directory" right after a fresh rebuild reported success. Now writes to $HOME/.ssh/<same filename>, a path the container user actually owns, and repoints the container's own copy of user.signingkey there instead of trying to recreate the host's path byte-for-byte. Verified with a real commit against the forwarded agent: signing succeeds and the object carries a gpgsig header. agent-forwarding.md updated to match: the host and container signingkey paths no longer need to be identical, since post-start.sh retargets the container's config to wherever it actually writes the file. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signing already worked. Verification never did, and its failure mode is
indistinguishable from signing being off, which is why it read as the
former:
$ git commit --allow-empty -S -m "Test signed commit"
$ git log --show-signature -1
error: gpg.ssh.allowedSignersFile needs to be configured and exist
for ssh signature verification
No signature
That `No signature` is the verifier reporting it could not run, not a
statement about the commit -- `git cat-file commit HEAD` on the very
same commit shows a `BEGIN SSH SIGNATURE` header. Nothing about the
signing path was broken, so every re-check of `commit.gpgsign`,
`user.signingkey` and the forwarded agent came back correct, and the
`error:` line that says so is easy to lose above the commit it precedes.
ssh-format verification needs a file mapping principals to the keys they
may sign with, and Git ships no default location for one. So write
`$HOME/.ssh/allowed_signers` next to the signing key post-start.sh
already sets up, listing `user.email` against that key, and point
`gpg.ssh.allowedSignersFile` at it.
Scoped to this user's own key deliberately: other contributors' commits
will report `No principal matched`, since verifying those means a shared
allowed-signers file, which is a project decision and not one a
container script should make on its own.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- The rebuild-wipes-it troubleshooting entry named only the signing-key file. `allowed_signers` and `gpg.ssh.allowedSignersFile` live under the same non-volume path and are wiped by a rebuild the same way, but weren't mentioned; a reader who rebuilt would find signing restored and verification silently not, with no entry pointing at why. Broadened the bullet and added a sibling entry for post-start.sh's other verification failure mode, missing user.email, to match the existing zero/multiple- identity entries in shape. - The new "No signature" subheading used a quoted phrase -- `### When show-signature says "No signature"` -- and both `[above]`/ `[below]` links pointed at `#when-show-signature-says-no-signature`. Rendering it through this repo's actual markdown-it-anchor pipeline shows the literal id is `when-show-signature-says-%22no-signature%22`: the slugifier percent-encodes quotes rather than stripping them, unlike GitHub's slugger. Both anchor links were dead. Dropped the quotes from the heading so the generated id matches what the links already assumed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
✅ Deploy Preview for gh-pages-openinf ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
CI's Verify Markdown step failed on these three: cspell has no dictionary entry for them and agent-forwarding.md's rewrites introduced all three. Added them to project-terms.txt in their alphabetical slots, the same as gpgsig picked up two commits ago on this branch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CI's Test step runs editorconfig-checker, which this branch had never actually exercised end to end -- the earlier commits landed before this was ever pushed as a PR, so the "Lint and test" job's markdown-only path was the only thing that had run against them. It requires every continuation line's indentation to be a multiple of two spaces, repo-wide, no per-language exception. Both "Setting up ... signing" sections used numbered lists whose continuation lines -- code fences and follow-on sentences under each step -- were indented 3 spaces, matching a single-digit `1. ` marker. That's also what Prettier's markdown printer produces and keeps idempotent for ordered lists, and it's CommonMark-correct, but 3 isn't a multiple of 2. Padding the marker to `1. ` (making the continuation indent 4) satisfies ec-checker but not markdownlint's MD030, which wants exactly one space after either marker. No combination of ordered-list spacing satisfies every tool at once. Switched both lists to `-` bullets instead, which this same doc's Troubleshooting section already uses with nested fences at a 2-space indent -- already proven compatible with all three tools. Numbering carried no meaning nothing else in the doc depended on (no "step 2" cross-references), so nothing is lost. Also caught by the same rule: the wrapped second line of the "No signature" error message inside its code fence was indented 7 spaces to align under "error: "; reindented to 2. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
OpenINFbot
approved these changes
Aug 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🐋 dev container
🔧 bug fix
Why
git commit -Sin the devcontainer produced a real signature, butgit log --show-signaturereportedNo signature-- a message that readsidentically to signing being broken, when the actual gap was that
SSH-format verification was never configured. Chasing that report as a
signing bug is a dead end:
commit.gpgsign,user.signingkey, and theforwarded agent were all already correct.
What this does
gpg.ssh.allowedSignersFileconfiguration topost-start.sh, sogit log --show-signaturecan verify what it signs instead of failingwith a message indistinguishable from unsigned commits.
agent-forwarding.mdfor the current stock devcontainer (nomore
sshd/vscodeuser/port 2222 from before 🐋♻️:replace the dev container with a stock image and features #1775), documents thecommit.gpgsignper-repo override footgun, and adds a"Verifying signatures locally" section plus matching troubleshooting
entries -- including one for the exact "No signature" trap above.
(
repoints,retargets,unconfigured) inproject-terms.txt.Test plan
~/.ssh/id_ed25519.pub,~/.ssh/allowed_signers, and therelated git config, then re-ran
post-start.shcold to confirm itrebuilds both from a fresh forwarded agent.
git commit --allow-empty -Sfollowed bygit log --show-signaturereports
Good "git" signature.user.emailbranch ofpost-start.shandconfirmed its warning fires without touching signing.
nps verify.md(prettier, markdownlint-cli2, remark, cspell) andnps verify.jsonboth clean locally.markdown-it+markdown-it-anchorpipeline to confirm in-doc anchor linksresolve.
🤖 Generated with Claude Code