Submodule Setup
Result:
parent-repo/
├── src/
├── docs/ <- submodule (always present)
└── exports/ <- submodule (always present)
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.
Some steps require human action in a browser (cannot be automated via CLI). When you reach these steps:
Handoff Protocol
- Announce the handoff - Clearly state what manual action is needed
- Provide direct URL - Give the exact URL to navigate to
- List exact steps - Number each click/selection in order
- Specify what to copy back - Tell user what value to provide after
- Wait explicitly - Use
AskUserQuestionto pause until user confirms - Verify completion - Run CLI command to confirm the manual step worked
Manual Steps in This Skill
| 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 |
Example Handoff Message
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.
After Manual Step
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'
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.
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.
Prerequisites
Before starting, run the validation script:
~/.claude/skills/submodule-setup/scripts/validate-prerequisites.sh
Required:
- GitHub CLI (
gh) installed and authenticated - Git 2.x+
- Repository with push access
- Ability to create fine-grained PAT
Phase 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)
Phase 0: Backup Checkpoint
Before any modifications, create recovery point:
~/.claude/skills/submodule-setup/scripts/create-backup.sh /path/to/parent-repo
This script:
- Verifies working tree is clean (aborts if dirty)
- Creates timestamped backup branch
- Saves rollback instructions to
.submodule-setup-backup
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.
Phase 1: Parameter Collection
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) |
Phase 2: Create Submodule Repos
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"
Phase 3: PAT and Secrets Setup
Step 1: Create Fine-Grained PAT (Manual - Browser Required)
This 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.
Step 2: Verify PAT Works
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.
Step 3: Set Secrets
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.
Phase 4: Content Migration (If Extracting)
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>
Phase 5: Add Submodules to Parent
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."
| 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 |
- Push Model: Each submodule pushes its SHA to parent via repository_dispatch. Parent updates its gitlink without cloning submodules. Works with private repos using only parent repo access.
- Pull Model: Parent periodically pulls latest from all submodules. Requires PAT with read access to every submodule repository.
Save choice to state: "phases.6.sync_model": "push" or "phases.6.sync_model": "pull"
Phase 6: Deploy GitHub Actions
Step 1: Select Sync Model
Use AskUserQuestion tool to determine sync model:
- If ANY submodule is private: recommend Push Model
- If ALL submodules are public: offer choice (Push Model still recommended for simplicity)
Step 2: Parent Repo Workflow
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:
- Replace
%DEFAULT_BRANCH%with actual branch
Step 3: Submodule Repo Workflows
Execute in parallel - write all workflow files simultaneously:
For each submodule, use Write tool to create .github/workflows/notify-parent.yml in the submodule directory:
- Read template from
~/.claude/skills/submodule-setup/assets/notify-parent.yml - Replace
%PARENT_REPO%with<org>/<parent-repo> - Replace
%DEFAULT_BRANCH%with actual branch - Replace
%TARGET_DIR%with submodule path in parent repo (e.g.,apps/docs)
Then commit and push each submodule's workflow.
Each submodule (check via API since we may not be in those directories)
gh 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
Phase 8: Claude Code Hooks (Optional)
Ask user if they want Claude Code hooks configured. If yes:
Use Read tool to check if .claude/settings.local.json exists.
- If exists: read and merge with new hooks
- If not: create new file
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"
}
]
}
]
}
}
Phase 9: Update Documentation
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:
- Repository structure table (directories → repos → purposes)
- Setup instructions for new clones
- Working with submodules workflow
- CI/CD checkout note (
submodules: recursive)
Phase 10: Validation
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.
Finalization
After Phase 10 completes successfully:
- Update state file with final status:
{
"current_phase": 10,
"phases.10.status": "completed",
"completed_at": "ISO8601 timestamp"
}
- Commit all changes to parent repo:
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
- Optionally remove state file (or keep for audit):
rm .submodule-setup-state.json # Optional
Quick Reference
| 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 |
Resources
Scripts
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 validation
Templates
assets/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 README
References
references/troubleshooting.md- Common issues and solutionsreferences/decision-rationale.md- Why submodules over alternatives
Source: bengous/claude-code-plugins — distributed by TomeVault.