Submodule "Permission denied (publickey)" over SSH in CI
By Kaveh Alemi·Latchkey
A submodule uses an SSH remote (git@github.com:...) and the runner has no private key SSH can present, so GitHub rejects the connection with "Permission denied (publickey)". HTTPS auth does not apply to SSH remotes.
What this error means
The parent repo checks out, then submodule update fails with "git@github.com: Permission denied (publickey). fatal: Could not read from remote repository." The .gitmodules URL is an git@ SSH URL.
git
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.
Please make sure you have the correct access rights and the repository exists.
fatal: clone of 'git@github.com:acme/shared.git' into submodule path 'vendor/shared' failed
Diagnose it: depth, refs, or credentials?
Terminal
git rev-parse --is-shallow-repository
git rev-parse --abbrev-ref HEAD # prints HEAD when detached
git log --oneline -3
git remote -v
Common causes
No SSH deploy key is loaded on the runner
The submodule remote is SSH, but nothing added a private key to an agent, so SSH offers no key and the server denies publickey auth.
The token-based checkout does not cover SSH submodules
actions/checkout injects an HTTPS token that rewrites github.com HTTPS URLs. An git@ SSH submodule URL is not rewritten by that token, so it still needs a key.
How to fix it
Load a deploy key with ssh-key on checkout
Add the submodule repo a read-only deploy key; store the private key as a secret.
Pass it to actions/checkout via ssh-key so the agent can authenticate SSH submodules.
Keep submodules: recursive so nested submodules are fetched too.
Pick one auth style for submodules: deploy key for SSH URLs, or token plus insteadOf for HTTPS.
Keep .gitmodules URLs consistent so one credential covers them all.
Give submodule deploy keys read-only scope.
Frequently asked questions
What causes Submodule "Permission denied (publickey)" over SSH in CI?
There are 2 common causes: no ssh deploy key is loaded on the runner and the token-based checkout does not cover ssh submodules. The submodule remote is SSH, but nothing added a private key to an agent, so SSH offers no key and the server denies publickey auth.
How do I fix Submodule "Permission denied (publickey)" over SSH in CI?
There are 2 fixes depending on which cause you have: load a deploy key with ssh-key on checkout and rewrite ssh submodule urls to authenticated https. Work through them in order, since the first is the most common.
What does Submodule "Permission denied (publickey)" over SSH in CI actually mean?
The parent repo checks out, then submodule update fails with "git@github.com: Permission denied (publickey).
How do I stop Submodule "Permission denied (publickey)" over SSH in CI happening again?
Pick one auth style for submodules: deploy key for SSH URLs, or token plus insteadOf for HTTPS. The prevention section lists 3 changes that keep it from recurring.