
Git Submodule
- 218 installs
- 40 repo stars
- Updated August 4, 2026
- akillness/oh-my-skills
Add, update, sync, and troubleshoot git submodules when vendoring shared libraries, themes, or multi-repo dependencies in application builds.
About
Guides agents through git submodule setup, initialization, updates, and common failure modes when projects depend on external repositories. It standardizes vendoring shared packages and keeping nested repos synchronized during active development.
- Submodule add and update flows
- Detached HEAD and sync fixes
- Multi-repo dependency management
- Vendor library integration patterns
- Safer agent-driven git operations
Git Submodule by the numbers
- 218 all-time installs (skills.sh)
- Ranked #169 of 733 Git & Pull Requests skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/akillness/oh-my-skills --skill git-submoduleAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 218 |
|---|---|
| repo stars | ★ 40 |
| Last updated | August 4, 2026 |
| Repository | akillness/oh-my-skills ↗ |
What it does
Add, update, sync, and troubleshoot git submodules when vendoring shared libraries, themes, or multi-repo dependencies in application builds.
Files
Git Submodule
Use this skill as the repository's Git submodule choice and operator-workflow anchor.
The job is not to dump every git submodule command in one blob. The job is to: 1. decide whether submodule is the right boundary at all, 2. choose one operating mode, 3. keep pinned-commit, detached-HEAD, and hosted-platform consequences explicit, 4. emit the next safe commands only for that mode.
Read references/decision-matrix.md first. Read references/update-and-detached-head.md when the request involves pointer updates, detached HEAD, or editing inside the submodule. Read references/ci-and-automation.md for CI/bootstrap expectations. Read references/mode-packets-and-hosted-constraints.md for mode-specific command packets and GitHub Pages / URL-drift constraints.
If the user mainly needs:
- local branch/history cleanup, rebase, conflict recovery, or push safety → use
git-workflow - Node package delivery from Git refs, tarballs, workspaces, or publish-first flows → use
npm-git-install - repo bootstrap/task-runner automation beyond submodule mechanics → use
workflow-automation - broader environment or container setup → use
system-environment-setup
When to use this skill
- Add an external repository to a project via
git submodule add - Clone or initialize a repository that already contains submodules
- Sync a submodule back to the commit recorded by the superproject
- Advance a submodule to a tracked remote branch and commit the new pointer
- Work inside a submodule without getting surprised by detached
HEAD - Remove a submodule cleanly
- Configure GitHub Actions or other CI to fetch submodules, including private ones
- Check hosted-platform constraints such as GitHub Pages public-submodule limits
- Decide whether submodule vs subtree vs vendoring vs package delivery is the better fit
When not to use this skill
- The main problem is ordinary Git collaboration, rebasing, or history repair
- The dependency should really be a package-manager / registry artifact
- The task is broader repo bootstrap automation rather than submodule mechanics
- The request is hosted repo administration outside submodule checkout / visibility constraints
- The user wants a giant Git tutorial instead of the next safe move for one submodule situation
Instructions
Step 1: Normalize the request
Capture the request in this form first:
submodule_intake:
current_goal: decide-boundary | add | bootstrap | sync-to-pinned-commit | advance-tracked-branch | edit-inside-submodule | remove | ci-checkout | hosted-constraint | unknown
repo_role: superproject-consumer | submodule-maintainer | both | unknown
dependency_shape: external-repo | vendor-copy | subtree-candidate | package-candidate | unknown
submodule_state: absent | present-uninitialized | present-detached-head | present-on-branch | pointer-needs-update | unknown
update_intent: none | match-recorded-commit | move-to-new-upstream-commit | develop-and-push-submodule | unknown
ci_context: none | github-actions | other-ci | github-pages | unknown
auth_context: public | private-ssh | private-token | unknown
collaboration_risk: solo | shared | unknown
confidence: high | medium | lowIf context is incomplete, make the safest default explicit.
Step 2: Decide whether submodule is the right tool
Answer these before giving commands:
- Do we need a separate upstream Git history inside this repo?
- Is exact commit pinning the real requirement?
- Can the team tolerate recursive clone/bootstrap and CI checkout setup?
- Is the dependency more like a subtree, a vendored snapshot, or a published package instead?
If the answer is "not really", route away instead of forcing submodules.
Step 3: Choose exactly one primary mode
Pick one primary mode for the current run: 1. boundary decision 2. add-and-pin 3. bootstrap-and-clone 4. sync-to-pinned-commit 5. advance-tracked-branch 6. edit-inside-submodule 7. remove-and-cleanup 8. ci-checkout 9. hosted-constraint
Use hosted-constraint when the user is blocked by platform rules such as GitHub Pages public-only submodules or stale submodule URL forms, rather than by ordinary local Git usage.
Step 4: Keep the operator invariants visible
These truths should survive every answer:
- A superproject records a submodule by commit, not by "latest branch".
git submodule updateusually restores the recorded commit and may leave the submodule in detachedHEAD..gitmodulesis part of the contract; branch-tracking intent belongs there whenupdate --remoteis expected.- A submodule commit is not reflected in the superproject until the submodule path is staged and committed there.
- Recursive bootstrap belongs in onboarding and automation docs if the repo depends on submodules.
- Hosted platforms may add visibility, URL, or auth constraints that normal local Git use does not reveal.
Step 5: Build the submodule brief
Return this exact structure:
# Git Submodule Brief
## Recommended mode
- Mode: boundary decision | add-and-pin | bootstrap-and-clone | sync-to-pinned-commit | advance-tracked-branch | edit-inside-submodule | remove-and-cleanup | ci-checkout | hosted-constraint
- Why this mode fits: ...
## Current state
- Superproject goal: ...
- Submodule state: ...
- Auth / CI / hosted context: ...
- Collaboration risk: solo | shared | unknown
- Confidence: high | medium | low
## Safest next move
1. ...
2. ...
3. ...
## Commands...
## Watch-outs
- ...
- ...
## Pointer / branch consequences
- ...
## Adjacent handoff
- `git-workflow` when ...
- `npm-git-install` when ...
- `workflow-automation` when ...Step 6: Use the mode packets, not a giant improvised command dump
Pull the exact packet from references/mode-packets-and-hosted-constraints.md.
Rules:
boundary decisionshould compare submodule with subtree / vendoring / package delivery directly.bootstrap-and-cloneandsync-to-pinned-commitmust preserve the difference between restore recorded state and upgrade pointer.advance-tracked-branchmust make branch intent explicit and commit the resulting pointer update in the superproject.edit-inside-submodulemust avoid detached-HEADcommit loss.ci-checkoutmust make private-submodule auth explicit.hosted-constraintmust call out public-only or URL-form restrictions instead of pretending hosted builds will authenticate like a local clone.
Output format
Return a short operator-style Git Submodule Brief.
Required qualities:
- pick one workflow mode
- say whether submodule is actually the right tool
- make detached-
HEAD/ pointer consequences explicit - include CI/auth or hosted-platform notes when relevant
- route away cleanly when the problem belongs to another skill
Examples
Example 1: bootstrap after clone
Input: "I cloned the repo and the vendor directory is empty. There's a .gitmodules file." Output: choose bootstrap-and-clone, recommend git submodule update --init --recursive, explain that this restores the pinned contents rather than upgrading anything, and mention private-auth caveats if applicable.
Example 2: update a tracked dependency
Input: "We track the main branch of a docs repo as a submodule and want the latest commit." Output: choose advance-tracked-branch, set or confirm submodule.<name>.branch, run git submodule update --remote <path>, then commit the pointer update in the superproject.
Example 3: choose boundary
Input: "Should this shared component repo be a submodule or subtree?" Output: choose boundary decision, compare separate-history/pinning needs against clone simplicity, and route to subtree if consumers should not deal with recursive bootstrap.
Example 4: hosted-platform constraint
Input: "Our GitHub Pages build uses a private docs-theme submodule and keeps failing." Output: choose hosted-constraint, explain the public-https:// GitHub Pages limitation, and route away from a private-submodule Pages design instead of pretending auth fixes it.
Best practices
1. Start with the boundary decision, not the command list. 2. Distinguish match pinned commit from advance upstream pointer. 3. Treat detached HEAD as normal-but-important operator state, not as a mysterious Git bug. 4. Keep submodule mechanics separate from generic Git history repair. 5. Make CI checkout, hosted-platform limits, and auth part of the main workflow whenever submodules are involved.
References
{
"skill_name": "git-submodule",
"evals": [
{
"id": 1,
"prompt": "I cloned a repo and the third_party/sdk directory is empty. There is a .gitmodules file. What should I do?",
"expected_output": "Chooses bootstrap-and-clone mode, recommends recursive init/update or clone --recurse-submodules, and explains that this restores the pinned contents rather than upgrading anything.",
"assertions": [
"Response identifies bootstrap/clone as the primary mode",
"Response includes `git submodule update --init --recursive` or `git clone --recurse-submodules`",
"Response mentions pinned commit or recorded state rather than treating this as an upgrade"
]
},
{
"id": 2,
"prompt": "We track another repo as a submodule and want to move it to the latest commit on its main branch. What is the safe flow?",
"expected_output": "Chooses advance-tracked-branch mode, makes branch tracking explicit, runs update --remote, and commits the resulting pointer change in the superproject.",
"assertions": [
"Response identifies advance-tracked-branch as the primary mode",
"Response references `.gitmodules` branch tracking or `git submodule set-branch`",
"Response stages and commits the submodule path in the superproject after update"
]
},
{
"id": 3,
"prompt": "Should we use a submodule or subtree for a shared internal component repo? We want exact pins, but teammates hate clone/setup friction.",
"expected_output": "Chooses boundary-decision mode, compares separate-history/pinning value against clone simplicity, and recommends the better fit instead of defaulting to submodule blindly.",
"assertions": [
"Response identifies boundary decision rather than jumping into add commands",
"Response compares submodule with subtree directly",
"Response includes at least one trade-off about recursive bootstrap, exact commit pinning, or separate history"
]
},
{
"id": 4,
"prompt": "Our GitHub Actions build can't find the private ui-kit submodule. How should checkout be configured?",
"expected_output": "Chooses ci-checkout mode, uses actions/checkout submodule configuration, and calls out auth for the private repo.",
"assertions": [
"Response identifies ci-checkout as the primary mode",
"Response includes `actions/checkout` with `submodules: recursive` or equivalent",
"Response mentions SSH key or token access for the private submodule"
]
},
{
"id": 5,
"prompt": "Our GitHub Pages build uses a private docs-theme submodule and keeps failing. Can we just add auth and keep going?",
"expected_output": "Chooses hosted-constraint mode, explains GitHub Pages' public https-only submodule limitation, and routes away from pretending private-submodule auth will work there.",
"assertions": [
"Response identifies hosted-constraint as the primary mode",
"Response mentions GitHub Pages specifically",
"Response explains that the submodule must be public and use https or equivalent public-read-only wording",
"Response does not present a normal CI token or SSH workaround as a sufficient fix"
]
}
]
}
CI and Automation Notes for Git Submodules
GitHub Actions checkout
actions/checkout does not fetch submodules unless you ask it to.
Public or same-credential submodules
- uses: actions/checkout@v5
with:
submodules: recursivePrivate submodules
You may need SSH or a token with access to every referenced repo.
- uses: actions/checkout@v5
with:
submodules: recursive
ssh-key: ${{ secrets.DEPLOY_KEY }}Automation rules
- If CI only needs the pinned contents, use recursive checkout and stop there.
- If CI is supposed to advance a submodule pointer, make the branch intent explicit and commit the resulting pointer change in the superproject.
- Treat private-submodule auth as part of the main workflow, not an afterthought.
Bootstrap scripts
For repo bootstrap scripts or Makefiles, prefer one obvious command:
git submodule update --init --recursiveIf the repo depends on this, document it in setup/onboarding rather than assuming contributors will remember.
Failure pattern checklist
- Missing files after clone → submodules were never initialized.
- CI works locally but not remotely → workflow checkout is missing
submodules:or credentials. - Submodule is on detached
HEADafter automation → expected when syncing to the pinned commit; only switch branches if you intend to edit inside it.
Git Submodule Decision Matrix
Use this note before emitting any git submodule commands.
Choose Git submodule when
- The external code must remain a separate Git repository with its own history.
- The superproject should pin an exact commit of that external repo.
- Consumers need a clear upstream boundary instead of a copied-in directory.
- The team accepts recursive clone/bootstrap and CI checkout setup as part of the workflow.
Prefer git subtree when
- Consumers should clone one repo and immediately have all code without submodule bootstrap.
- You still want to pull/merge upstream history, but the separate checkout boundary is more pain than value.
- The dependency behaves more like a long-lived integrated component than a separately operated repo.
Prefer vendoring / checked-in copy when
- The dependency is tiny, changes rarely, or only needs occasional manual refresh.
- The team values clone simplicity over preserved upstream Git history.
- Upstream sync does not justify extra Git plumbing.
Prefer package-manager / artifact delivery when
- The real goal is consuming a build artifact or published library, not another repo's raw history.
- Onboarding, CI reproducibility, or cross-team consumption matters more than direct repo-level editing.
- The dependency is already in the Node / Python / package-manager lane.
Fast decision checklist
| Question | If yes | If no |
|---|---|---|
| Do we need a separate upstream Git history inside this repo? | keep evaluating submodule | use subtree / vendoring / package delivery |
| Is exact commit pinning the primary requirement? | submodule is plausible | other mechanisms may be simpler |
| Can the team support recursive clone/bootstrap + CI checkout? | submodule still viable | prefer subtree or artifact flow |
| Will contributors actively edit code inside the external repo? | plan for branch checkout + pointer commits | ordinary pinned-consumer flow may be enough |
Boundary reminders
- Generic branch/history cleanup belongs to
git-workflow. - Node package delivery choices belong to
npm-git-install. - Repo bootstrap automation belongs to
workflow-automationwhen the problem expands beyond submodule mechanics.
Git Submodule Mode Packets and Hosted Constraints
Use this reference after SKILL.md chooses the primary mode.
Mode packets
1. boundary decision
Use when the real question is submodule vs subtree vs vendoring vs package/artifact delivery.
Operator rule:
- Prefer submodule when separate repo history and exact commit pinning are non-negotiable.
- Prefer subtree when consumers should clone one repo with no recursive bootstrap.
- Prefer vendoring when the dependency is tiny or rarely updated.
- Prefer package-manager / artifact delivery when the real need is consuming a library, not embedding another repo boundary.
2. add-and-pin
git submodule add <repo-url> <path>
git add .gitmodules <path>
git commit -m "feat: add <name> as submodule"Optional explicit branch intent:
git submodule set-branch --branch main <path>
git add .gitmodules
git commit -m "chore: set submodule branch intent"Watch-out: the superproject still pins a commit, not "latest".
3. bootstrap-and-clone
git clone --recurse-submodules <repo-url>
# or inside an existing clone
git submodule update --init --recursiveUse for onboarding, fresh machines, and CI/bootstrap repair.
4. sync-to-pinned-commit
git submodule update --init --recursive
git submodule status --recursive
git diff --submoduleWatch-out: detached HEAD is normal here because Git is restoring the recorded commit.
5. advance-tracked-branch
git submodule set-branch --branch main path/to/submodule
git submodule update --remote path/to/submodule
git add .gitmodules path/to/submodule
git commit -m "chore: update submodule pointer"Watch-outs:
update --remoteintentionally changes the pinned pointer.- Review the resulting gitlink diff before committing.
6. edit-inside-submodule
cd path/to/submodule
git switch main # or another real branch
# make / commit / push submodule changes here
cd -
git add path/to/submodule
git commit -m "chore: update submodule pointer"Watch-out: do not leave contributors in detached HEAD if they intend to commit inside the submodule.
7. remove-and-cleanup
git submodule deinit -f path/to/submodule
git rm -f path/to/submodule
rm -rf .git/modules/path/to/submodule
git commit -m "chore: remove submodule"Watch-out: .gitmodules state and .git/modules/... cleanup are both part of a real removal.
8. ci-checkout
For GitHub Actions:
- uses: actions/checkout@v5
with:
submodules: recursiveFor private submodules, add the chosen auth story explicitly:
- uses: actions/checkout@v5
with:
submodules: recursive
ssh-key: ${{ secrets.DEPLOY_KEY }}Alternative: use a token with access to every referenced repo when SSH is not the right fit.
Hosted-platform constraints
GitHub Pages
GitHub Pages only supports submodules when they point to public repositories over read-only https:// URLs.
Checklist:
- keep
.gitmodulesURLs onhttps:// - ensure nested submodules are also public and
https:// - if the dependency must stay private, route away from a GitHub Pages submodule design instead of pretending auth will work
URL drift / nested drift
Use this when upstream URLs changed or nested submodules still point at stale remotes:
git submodule sync --recursive
git submodule update --init --recursiveReview commands
git submodule status --recursive
git diff --submodule
git submodule summary
git config -f .gitmodules --get-regexp '^submodule\..*\.branch$'Boundary reminders
- Generic branch/history cleanup belongs to
git-workflow. - Package delivery belongs to
npm-git-installor the relevant package-manager skill. - Wider bootstrap/task-runner automation belongs to
workflow-automation.
Update Flow and Detached-HEAD Notes
Core truth
A superproject records a submodule by commit, not by “latest branch”. That is why normal git submodule update checks out the recorded commit and can leave the submodule in detached `HEAD` state.
Bootstrap patterns
# Fresh clone with all submodules
git clone --recurse-submodules <repo-url>
# Existing clone missing submodules
git submodule update --init --recursiveDaily sync modes
1. Match the superproject's recorded commit
Use when the goal is reproducibility, onboarding, or CI parity.
git submodule update --init --recursive2. Advance to the tracked remote branch
Use only when you intentionally want to move the pointer.
git submodule set-branch --branch main path/to/submodule
git submodule update --remote path/to/submoduleThen review and commit the new pointer from the superproject:
git add .gitmodules path/to/submodule
git commit -m "chore: update submodule pointer"Detached-HEAD operator rule
If you need to make or keep commits inside the submodule, do not stay detached.
cd path/to/submodule
git switch main # or another real branch
# make/commit/push submodule changes
cd -
git add path/to/submodule
git commit -m "chore: update submodule pointer"Review commands
git submodule status --recursive
git diff --submodule
git submodule summaryWatch-outs
git submodule update --remoteis not a harmless refresh; it changes the pointer you are pinning.- If
.gitmodulesdoes not set the branch explicitly, collaborators may assume the wrong upgrade target. - A submodule commit is not part of the superproject until you stage and commit the submodule path there.
N:git-submodule
D:Decide when Git submodules are the right external-repo boundary, then choose one safe operator flow: add and pin, bootstrap recursively, sync the recorded commit, advance a tracked branch, avoid detached-HEAD surprises while editing inside the submodule, remove cleanly, or handle CI / hosted-platform checkout constraints. Use for `.gitmodules`, `git submodule`, recursive clone/setup, pointer updates, detached HEAD, private submodules in CI, GitHub Pages limits, or submodule-vs-subtree/vendor decisions.
G:git submodule subtree dependencies version-control ci repo-structure github-pages
U[5]:
Add or pin an external repository as a submodule
Bootstrap or sync a repo with existing submodules safely
Advance a tracked branch and commit the pointer update
Work inside a submodule without detached HEAD confusion
Handle CI or hosted-platform constraints for submodules
S[7]{n,action}:
1,Normalize the submodule request
2,Decide whether submodule is the right tool
3,Choose one workflow mode
4,Keep pinned-commit and detached-HEAD invariants visible
5,Build the Git Submodule Brief
6,Use the mode packets and hosted-constraint reference
7,Route adjacent problems to the right skill