Offboarding a developer from every repository Jump to heading
Removing someone from the organisation revokes their account and leaves behind everything that was theirs but is not the account: a personal token a deployment depends on, a signing key in an allowed-signers file, a deploy key they created, ownership entries naming them, and branches whose only copy is on their laptop. Miss the first and the deployment keeps working with a departed personβs credentials; miss the last and work is lost. This recipe covers the whole list, within repository access control and policy.
When to use this approach Jump to heading
- Someone is leaving, or has left and the cleanup was partial.
- An audit found credentials belonging to a former colleague.
- A pipeline broke after an offboarding and nobody knew why.
- You are writing the offboarding procedure and want the Git-specific parts.
- If the person had read-only access and no automation, account removal genuinely is the whole task.
Step 1 β Find what depends on them before removing anything Jump to heading
Removing the account first turns a checklist into an incident.
# Personal tokens used by shared automation, by name
for r in $(gh repo list acme --limit 200 --json name --jq '.[].name'); do
gh api "repos/acme/$r/actions/secrets" --jq ".secrets[] | select(.name | test(\"PAT|TOKEN\")) | \"$r \(.name)\"" 2>/dev/null
done | head -20 # Deploy keys they created
for r in $(gh repo list acme --limit 200 --json name --jq '.[].name'); do
gh api "repos/acme/$r/keys" --jq ".[] | select(.title | test(\"$USERNAME\"; \"i\")) | \"$r \(.title)\"" 2>/dev/null
done # Ownership entries naming them
for r in $(gh repo list acme --limit 200 --json name --jq '.[].name'); do
gh api "repos/acme/$r/contents/.github/CODEOWNERS" --jq '.content' 2>/dev/null \
| base64 -d 2>/dev/null | grep -l "@$USERNAME" >/dev/null 2>&1 && echo "$r"
done # Verification: the list is complete before the account is touched Step 2 β Collect their unpushed work first Jump to heading
This is the step with a deadline: once the laptop is returned or wiped, it is gone.
# On their machine, before it is handed back
git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads \
| grep -v '\[gone\]' | grep -E 'ahead|^\S+$' # Push everything that has no remote counterpart
git for-each-ref --format='%(refname:short)' refs/heads | while read -r b; do
git push -u origin "$b" 2>/dev/null || echo "could not push: $b"
done # And the stash, which is not pushed by anything
git stash list
git stash list --format='%gd' | while read -r s; do
git stash branch "leaving/$(echo "$s" | tr -d '@{}')" "$s" && git push -u origin HEAD
done # Verification: nothing local is ahead of its remote
git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads | grep ahead || echo "all pushed" Step 3 β Replace their credentials in shared automation Jump to heading
# A personal token in a repository secret must be replaced, not just revoked
gh api -X PUT "repos/acme/app/actions/secrets/DEPLOY_TOKEN" \
-f encrypted_value="$NEW_ENCRYPTED" -f key_id="$KEY_ID" # Better: replace it with a scoped app token so this never recurs
# (see the parent topic on scoping credentials) # Verification: pipelines still pass after the replacement, before the account goes
gh run list --limit 10 --json conclusion --jq 'group_by(.conclusion)[] | {r: .[0].conclusion, n: length}' SAFETY WARNING β do not revoke a personal token that shared automation depends on without replacing it first. The failure is a deployment pipeline that stops working at an unpredictable moment, usually discovered during the next incident. Replace, verify, then revoke β and treat the existence of the token as a finding to fix rather than a fact to work around.
Step 4 β Update signing trust and ownership Jump to heading
# Allowed-signers: add a validity window rather than deleting the line,
# so their historical commits still verify
sed -i "s|^$EMAIL |$EMAIL valid-before=\"$(date -u +%Y%m%d%H%M%S)\" |" .github/allowed_signers
git commit -am "chore: bound the validity of a departing colleague's signing key" # Ownership entries: replace with the owning team, not with another individual
sed -i "s|@$USERNAME|@acme/platform|g" .github/CODEOWNERS
git commit -am 'chore: transfer ownership entries to the team' # Verification: history still verifies, and no path is unowned
git verify-commit "$(git log --author="$EMAIL" --format=%H -1)" 2>&1 | tail -1
grep -c "@$USERNAME" .github/CODEOWNERS The validity window is the detail that matters: deleting the key line means every commit they ever signed stops verifying, which rewrites the meaning of your history. Bounding it preserves the past while ending future trust β the model is described in commit verification gates.
Step 5 β Remove the account, then verify Jump to heading
gh api -X DELETE "orgs/acme/members/$USERNAME" # Re-run the access audit for every repository they touched
git log --format='%ae' --all | grep -c "$EMAIL"
gh repo list acme --limit 200 --json name --jq '.[].name' | while read -r r; do
gh api "repos/acme/$r/collaborators/$USERNAME" --silent 2>/dev/null && echo "STILL PRESENT: $r"
done # And confirm nothing broke
gh run list --limit 20 --json conclusion,workflowName \
--jq '[.[] | select(.conclusion=="failure")] | length' # Verification: the full audit from the parent topic returns no trace Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Does removing the account revoke their tokens? Jump to heading
Usually the personal ones, and not necessarily immediately, and provider behaviour varies enough that relying on it is unwise. The safer assumption is that anything they created continues to work until explicitly revoked, which is why the inventory comes first and the removal last.
What about their commits β should authorship change? Jump to heading
No. Commits record who wrote them and that remains true after someone leaves; rewriting authorship would falsify history and invalidate every signature. If their address needs to display differently, a .mailmap entry does it without touching the commits β the approach in rewriting author emails during a migration.
How do we handle a contractor with access to many organisations? Jump to heading
The same checklist per organisation, and the personal-token problem is more acute because contractors frequently set up automation under their own identity. Making app-based or repository-scoped credentials the standard for automation removes the whole category, and is worth doing before the offboarding rather than during it.
Related Jump to heading
- Repository Access Control & Policy β the parent topic and the routes to audit.
- Auditing Who Can Push to Protected Branches β the check that confirms the offboarding was complete.
- Rotating a Compromised Commit-Signing Key β the validity-window mechanics in full.