From git-tools
This skill should be used when the user asks to "set up submodules", "migrate branches to submodules", "automate submodule sync", "create submodule architecture", "convert branches to repos", or mentions setting up git submodules with GitHub Actions automation for multi-repo synchronization.
npx claudepluginhub bengous/claude-code-plugins --plugin git-toolsThis skill uses the workspace's default tool permissions.
<overview>
assets/README-template.mdassets/notify-dependent.ymlassets/notify-parent.ymlassets/update-submodules-push.ymlassets/update-submodules.ymlreferences/decision-rationale.mdreferences/troubleshooting.mdscripts/check-nested-repos.shscripts/create-backup.shscripts/setup-dev.shscripts/test-scripts.shscripts/validate-prerequisites.shVerifies tests pass on completed feature branch, presents options to merge locally, create GitHub PR, keep as-is or discard; executes choice and cleans up worktree.
Guides root cause investigation for bugs, test failures, unexpected behavior, performance issues, and build failures before proposing fixes.
Writes implementation plans from specs for multi-step tasks, mapping files and breaking into TDD bite-sized steps before coding.
Share bugs, ideas, or general feedback.
Result:
parent-repo/
├── src/
├── docs/ <- submodule (always present)
└── exports/ <- submodule (always present)
<tool_usage>
Use these tools for each operation type:
| Operation | Tool | Parallelization |
|---|---|---|
| Collect parameters | AskUserQuestion | Single call with all parameters |
| Create GitHub repos | Bash | Parallel - no dependencies between repos |
| Set secrets | Bash | Parallel - no dependencies between repos |
| Deploy workflow files | Write | Parallel - independent files |
| Run git commands | Bash | Sequential within phase, parallel across unrelated ops |
| Validate setup | Bash | Sequential - each check depends on prior state |
| Read/write state | Read/Write | As needed for checkpoint operations |
Parallel execution rule: If two or more commands have no dependencies between them, execute them in a single message with multiple tool calls. </tool_usage>
<manual_intervention>
Some steps require human action in a browser (cannot be automated via CLI). When you reach these steps:
AskUserQuestion to pause until user confirms| Phase | Manual Action | URL |
|---|---|---|
| Phase 3 | Create Fine-Grained PAT | https://github.com/settings/personal-access-tokens/new |
| Phase 6 | Configure GitHub Pages (if needed) | https://github.com/{owner}/{repo}/settings/pages |
I need you to create a Personal Access Token in the browser.
**URL:** https://github.com/settings/personal-access-tokens/new
**Steps:**
1. Click "Generate new token"
2. Token name: `submodule-sync-myproject`
3. Expiration: Select "90 days"
4. Repository access: Click "Only select repositories"
5. Select: `myorg/parent-repo` (and each submodule repo if using Pull Model)
6. Permissions → Repository permissions → Contents: "Read and write"
7. Click "Generate token"
8. **Copy the token** (starts with `github_pat_`)
Paste the token below when ready.
Always verify the manual action succeeded:
# After PAT creation - test it works
gh auth status # or test API call with new token
# After GitHub Pages config
gh api repos/{owner}/{repo}/pages --jq '.source'
</manual_intervention>
<state_schema>
Location: .submodule-setup-state.json in parent repo root.
Use Read tool to check for existing state on resume. Use Write tool to update after each phase.
{
"version": "1.0",
"started_at": "2026-01-13T10:30:00Z",
"current_phase": 2,
"params": {
"PARENT_REPO": "org/repo",
"SUBMODULE_NAMES": ["docs", "exports"],
"TARGET_DIRS": ["docs", "exports"],
"DEFAULT_BRANCH": "dev",
"SOURCE_BRANCHES": [],
"SOURCE_PATHS": []
},
"phases": {
"0": { "status": "completed", "backup_branch": "backup/pre-submodule-setup-20260113-103000" },
"1": { "status": "completed" },
"2": { "status": "in_progress", "repos_created": ["org/repo-docs"], "repos_pending": ["org/repo-exports"] }
},
"audit_log": [
{ "event": "phase_started", "phase": 0, "timestamp": "2026-01-13T10:30:00Z" },
{ "event": "backup_created", "branch": "backup/pre-submodule-setup-20260113-103000", "timestamp": "2026-01-13T10:30:05Z" }
]
}
Resume logic: If state file exists, read current_phase and skip completed phases. Resume from in_progress phase using saved progress data.
</state_schema>
<escalation_defaults>
These patterns apply to all phases unless overridden:
| Error Pattern | Action | Max Retries |
|---|---|---|
rate limit | Retry with exponential backoff | 3 |
network error / timeout | Retry | 3 |
already exists | Ask user: use existing, rename, or abort | - |
permission denied | Abort with auth instructions | 0 |
not found | Ask user to verify input | - |
| Unknown error | Abort and report | 0 |
Abort behavior: On abort, update state file with "status": "failed" and error details. Do not delete state file - it aids debugging.
</escalation_defaults>
Before starting, run the validation script:
~/.claude/skills/submodule-setup/scripts/validate-prerequisites.sh
Required:
gh) installed and authenticatedPhase 0: Backup → Phase 1: Parameters → Phase 2: Create Repos → Phase 3: PAT/Secrets
↓
Phase 10: Validate ← Phase 9: Docs ← Phase 8: Hooks ← Phase 7: Scripts ← Phase 6: Actions
↑
Phase 5: Add Submodules ← Phase 4: Migrate (conditional)
<decision_criteria id="phase4"> Phase 4 Decision:
SOURCE_BRANCHES in Phase 1SOURCE_BRANCHES is empty or user says "start fresh"
</decision_criteria><resume_behavior> On Resume:
.submodule-setup-state.json"status": "in_progress"repos_created list)"status": "pending" phase
</resume_behavior>
Before any modifications, create recovery point:
~/.claude/skills/submodule-setup/scripts/create-backup.sh /path/to/parent-repo
This script:
.submodule-setup-backup<error_recovery> If this fails:
~/.claude/skills/submodule-setup/
</error_recovery>Rollback procedure (if setup fails mid-way):
# Reset parent repo to pre-submodule state
git reset --hard backup/pre-submodule-setup-<timestamp>
git submodule deinit -f --all 2>/dev/null || true
rm -rf .git/modules/* 2>/dev/null || true
# Delete created GitHub repos (if needed)
gh repo delete <org>/<submodule-repo> --yes
See references/troubleshooting.md for detailed rollback scenarios.
Use AskUserQuestion tool to collect all parameters in a single call:
| Parameter | Example | Notes |
|---|---|---|
PARENT_REPO | myorg/myproject | Full GitHub path |
SUBMODULE_NAMES | docs exports | Space-separated |
TARGET_DIRS | docs exports | Where to mount (usually same as names) |
DEFAULT_BRANCH | dev | Branch to track |
SOURCE_BRANCHES | docs-external,n8n-exports | Only if extracting (leave empty for fresh start) |
SOURCE_PATHS | docs-external/,exports/ | Paths within branches (only if extracting) |
<error_recovery>
If unclear: Ask user to clarify. Do not assume values for SOURCE_BRANCHES - this controls Phase 4 execution.
</error_recovery>
Execute in parallel - create all repos simultaneously with multiple Bash tool calls in one message:
# For each submodule (run these in parallel)
gh repo create <org>/<parent-name>-<submodule> \
--private \
--description "Submodule: <description>"
Example (execute both in parallel):
gh repo create myorg/myproject-docs --private --description "External documentation"
gh repo create myorg/myproject-exports --private --description "Exported workflows"
**Verification:** For each repo, confirm creation succeeded:
```bash
gh repo view / --json name -q '.name'
```
Expected: Returns repo name without error. Do not proceed to Phase 3 until all repos exist.
<error_recovery> If this fails:
gh auth status, verify org permissionsThis step requires human action. Use AskUserQuestion with this handoff:
I need you to create a Personal Access Token in your browser.
**Open this URL:** https://github.com/settings/personal-access-tokens/new
**Follow these steps exactly:**
1. Click **"Generate new token"** (fine-grained)
2. **Token name:** `submodule-sync-{PARENT_REPO_NAME}`
3. **Expiration:** Select "90 days" (set a calendar reminder!)
4. **Repository access:** Click "Only select repositories"
5. **Select repositories:**
- `{ORG}/{PARENT_REPO}` (required)
- For Pull Model only: also select each submodule repo
6. **Permissions** → Repository permissions:
- **Contents:** Read and write
- **Workflows:** Read and write (optional but recommended)
7. Click **"Generate token"** at the bottom
8. **IMPORTANT:** Copy the token immediately (starts with `github_pat_`)
- You won't be able to see it again!
**Paste the token when ready** (I'll store it securely as a GitHub secret).
Wait for user to provide the PAT before proceeding.
After receiving the PAT, verify it has correct permissions:
# Test PAT can access parent repo
# Use read -rs to avoid token in shell history
read -rs PAT && echo
GH_TOKEN="$PAT" gh api repos/{ORG}/{PARENT_REPO} --jq '.full_name'
If this fails, guide user to check token permissions.
Execute in parallel - set all secrets simultaneously.
Option A (recommended): Use interactive prompt - no token in history:
gh secret set PARENT_REPO_PAT --repo <org>/<parent>
gh secret set PARENT_REPO_PAT --repo <org>/<submodule-1>
gh secret set PARENT_REPO_PAT --repo <org>/<submodule-2>
# Each prompts for the secret value interactively
Option B: If automating with a variable (already captured securely):
# PAT must already be in $PAT variable (from read -rs above)
printf '%s' "$PAT" | gh secret set PARENT_REPO_PAT --repo <org>/<parent>
printf '%s' "$PAT" | gh secret set PARENT_REPO_PAT --repo <org>/<submodule-1>
printf '%s' "$PAT" | gh secret set PARENT_REPO_PAT --repo <org>/<submodule-2>
unset PAT # Clear from environment when done
Security note: Never type the PAT directly in a command - it will be recorded in shell history. Use read -rs to capture it into a variable first, or rely on gh secret set's interactive prompt.
<error_recovery> If this fails:
<decision_criteria>
Skip this phase if: SOURCE_BRANCHES from Phase 1 is empty or user specified "start fresh"
</decision_criteria>
For each submodule with source branch (execute sequentially per submodule):
# Extract from branch
cd /path/to/parent-repo
rm -rf /tmp/extract && mkdir -p /tmp/extract
git archive origin/<source-branch>:<source-path> | tar -x -C /tmp/extract/
# Clone and populate new repo
cd /tmp
git clone git@github.com:<org>/<submodule-repo>.git
cd <submodule-repo>
git checkout -b <default-branch>
cp -r /tmp/extract/* .
git add .
git commit -m "feat: initial content from <source-branch>"
git push -u origin <default-branch>
# Set default branch
gh repo edit <org>/<submodule-repo> --default-branch <default-branch>
**Verification:** For each submodule repo:
```bash
gh api repos///branches/ -q '.name'
```
Expected: Returns branch name. Also verify content exists:
```bash
gh api repos///contents -q '.[].name' | head -5
```
Expected: Lists files from extracted content.
<error_recovery> If this fails:
git branch -rgit ls-tree origin/<branch>cd /path/to/parent-repo
git checkout <default-branch>
# For each submodule (execute sequentially - git operations require serial execution)
git submodule add -b <default-branch> \
git@github.com:<org>/<submodule-repo>.git \
<target-dir>
git commit -m "feat(repo): add submodules for <list>
Migrate from branch-based separation to submodule architecture.
Enables agents to access content without branch switching."
**Verification:**
```bash
git submodule status
```
Expected: Each submodule listed with commit hash (no `-` prefix indicating uninitialized).
<error_recovery> If this fails:
--forcegit submodule update --remote
</error_recovery>
<decision_criteria id="phase6-sync-model"> Sync Model Selection: Ask the user which sync model to use:
| Model | Use When | PAT Scope |
|---|---|---|
| Push Model (Recommended) | Any submodule is private, or you want minimal PAT scope | Parent repo only |
| Pull Model | All submodules are public AND you prefer simpler setup | Parent + all submodule repos |
Save choice to state: "phases.6.sync_model": "push" or "phases.6.sync_model": "pull"
</decision_criteria>
Use AskUserQuestion tool to determine sync model:
For Push Model:
Use Read tool to get template from ~/.claude/skills/submodule-setup/assets/update-submodules-push.yml.
For Pull Model:
Use Read tool to get template from ~/.claude/skills/submodule-setup/assets/update-submodules.yml.
Use Write tool to create .github/workflows/update-submodules.yml:
%DEFAULT_BRANCH% with actual branchExecute in parallel - write all workflow files simultaneously:
For each submodule, use Write tool to create .github/workflows/notify-parent.yml in the submodule directory:
~/.claude/skills/submodule-setup/assets/notify-parent.yml%PARENT_REPO% with <org>/<parent-repo>%DEFAULT_BRANCH% with actual branch%TARGET_DIR% with submodule path in parent repo (e.g., apps/docs)Then commit and push each submodule's workflow.
**Verification:** Confirm workflow files exist: ```bash # Parent repo ls -la .github/workflows/update-submodules.ymlgh api repos///contents/.github/workflows/notify-parent.yml -q '.name'
Expected: Files exist and contain correct branch names.
</verification>
<error_recovery>
**If this fails:**
- File not created: Verify `.github/workflows/` directory exists, create if needed
- Push rejected: Verify PAT has `workflows` permission
</error_recovery>
</phase>
---
<phase name="7" title="Deploy Local Scripts">
<progress>
<status_key>phase_7_scripts</status_key>
<on_start>Creating local helper scripts</on_start>
<on_complete>Helper scripts deployed and made executable</on_complete>
<trackable_items>
<item key="scripts_created" type="counter" target="2"/>
</trackable_items>
</progress>
<escalation>
<retry max="1">
<condition>directory does not exist</condition>
<action>Create scripts/ directory, then retry</action>
</retry>
</escalation>
<checkpoint>
<on_complete>
- "phases.7.status": "completed"
- "phases.7.scripts": ["setup-dev.sh", "check-nested-repos.sh"]
</on_complete>
</checkpoint>
<audit>
<event name="script_deployed">
<fields>script_name, timestamp</fields>
</event>
</audit>
## Phase 7: Deploy Local Scripts
Use `Read` tool to get templates, `Write` tool to create scripts, then `Bash` for chmod:
**setup-dev.sh:**
- Read template from `~/.claude/skills/submodule-setup/scripts/setup-dev.sh`
- Replace `%SUBMODULES%` → actual submodule list (space-separated)
- Replace `%DEFAULT_BRANCH%` → actual branch
- Write to `scripts/setup-dev.sh`
**check-nested-repos.sh:**
- Read template from `~/.claude/skills/submodule-setup/scripts/check-nested-repos.sh`
- Replace `%SUBMODULES%` → actual submodule list (space-separated)
- Write to `scripts/check-nested-repos.sh`
Make executable:
```bash
chmod +x scripts/setup-dev.sh scripts/check-nested-repos.sh
**Verification:**
```bash
ls -la scripts/*.sh | grep -E 'setup-dev|check-nested'
```
Expected: Both files listed with execute permission (`-rwxr-xr-x`).
<error_recovery> If this fails:
scripts/ directory firstAsk user if they want Claude Code hooks configured. If yes:
Use Read tool to check if .claude/settings.local.json exists.
Use Write tool to create or update .claude/settings.local.json:
{
"permissions": {
"allow": [
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git push)"
]
},
"hooks": {
"PostToolUse": [
{
"matcher": "Bash(git commit:*)",
"hooks": [
{
"type": "command",
"command": "scripts/check-nested-repos.sh"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "scripts/check-nested-repos.sh --end-of-task"
}
]
}
]
}
}
**Verification:**
```bash
grep -c "check-nested-repos" .claude/settings.local.json
```
Expected: Returns 2 (appears twice in hooks config).
<error_recovery> If this fails:
Add submodule documentation to project's README.md or CLAUDE.md.
Use Read tool to get template from ~/.claude/skills/submodule-setup/assets/README-template.md.
Use Read tool to get existing README.md content.
Use Edit tool to append submodule section to existing README (do not overwrite).
Key sections to include:
submodules: recursive)<error_recovery> If this fails:
Execute validation checklist sequentially:
# 1. Submodules initialized
git submodule status
# Expected: No '-' prefix (initialized)
# 2. Git config applied
git config --get submodule.recurse # Expected: true
git config --get push.recurseSubmodules # Expected: on-demand
# 3. Test push flow
cd <submodule>
echo "test" >> test.md
git add . && git commit -m "test: verify sync"
git push
cd ..
# Wait 2-3 minutes, then:
git fetch && git log --oneline -3
# Expected (Pull Model): "chore(submodules): auto-update to latest [skip ci]"
# Expected (Push Model): "chore: update <submodule-name> submodule"
# 4. Test fail-fast script
cd <submodule>
echo "uncommitted" >> temp.md
cd ..
./scripts/check-nested-repos.sh
# Expected: Exit 1 with "NESTED REPOS: ACTION REQUIRED"
# 5. Cleanup (local files only)
cd <submodule> && rm test.md temp.md 2>/dev/null; git checkout .; cd ..
# Note: The "test: verify sync" commit remains in submodule history.
# This is acceptable - it verifies the sync actually works end-to-end.
**Final success criteria:**
- [ ] All submodules show commit hash (no `-` prefix) in `git submodule status`
- [ ] Push to submodule triggers parent auto-update within 3 minutes
- [ ] `check-nested-repos.sh` detects uncommitted changes
- [ ] All workflow files deployed to correct repos
After Phase 10 completes successfully:
{
"current_phase": 10,
"phases.10.status": "completed",
"completed_at": "ISO8601 timestamp"
}
git add .
git commit -m "feat(repo): complete submodule setup
- Added submodules: <list>
- Configured GitHub Actions for auto-sync
- Added helper scripts and documentation"
git push
rm .submodule-setup-state.json # Optional
| Task | Command |
|---|---|
| Clone with submodules | git clone --recurse-submodules <url> |
| Init after clone | ./scripts/setup-dev.sh |
| Update submodules | git submodule update --remote |
| Check submodule status | git submodule status |
| Check for uncommitted | ./scripts/check-nested-repos.sh |
| Check for unpushed | ./scripts/check-nested-repos.sh --end-of-task |
| Checkout submodule branch | cd <submodule> && git checkout <branch> |
| Force re-init | git submodule update --init --recursive --force |
| Resume interrupted setup | Read .submodule-setup-state.json and continue from current_phase |
scripts/validate-prerequisites.sh - Run before starting setupscripts/create-backup.sh - Create backup checkpoint before modificationsscripts/setup-dev.sh - Template for new clone initializationscripts/check-nested-repos.sh - Template for commit validationassets/update-submodules.yml - GitHub Action for parent repo (Pull Model)assets/update-submodules-push.yml - GitHub Action for parent repo (Push Model)assets/notify-parent.yml - GitHub Action for each submoduleassets/notify-dependent.yml - GitHub Action for cross-repo notificationsassets/README-template.md - Documentation template for project READMEreferences/troubleshooting.md - Common issues and solutionsreferences/decision-rationale.md - Why submodules over alternatives