Skip to content

fix: resolve default branch explicitly when updating shallow-cloned custom modules#2332

Open
jrevillard wants to merge 1 commit intobmad-code-org:mainfrom
jrevillard:fix/shallow-clone-update-default-branch
Open

fix: resolve default branch explicitly when updating shallow-cloned custom modules#2332
jrevillard wants to merge 1 commit intobmad-code-org:mainfrom
jrevillard:fix/shallow-clone-update-default-branch

Conversation

@jrevillard
Copy link
Copy Markdown
Contributor

@jrevillard jrevillard commented Apr 27, 2026

Summary

  • With --depth 1 clones, origin/HEAD becomes stale after the initial clone. The update path used git reset --hard origin/HEAD which never picked up new commits pushed to the default branch.
  • Fix resolves the default branch name via git symbolic-ref refs/remotes/origin/HEAD, then fetches and resets against origin/<branch> explicitly. Falls back to main if origin/HEAD is not set.

How to reproduce

  1. Install a custom module without a version suffix: npx bmad-method install --custom-source https://github.com/user/custom-module
  2. Push new commits to main on the custom module repo
  3. Re-run the install command → cache shows stale content (old commit)

Root cause

git clone --depth 1 creates a shallow clone. In shallow clones, origin/HEAD is a fixed reference to the initial commit and does not update with git fetch origin --depth 1. The subsequent git reset --hard origin/HEAD therefore always resets to the original commit.

Fix

// Before (broken with shallow clones)
execSync('git fetch origin --depth 1', { cwd: repoCacheDir });
execSync('git reset --hard origin/HEAD', { cwd: repoCacheDir });

// After (fetches the actual default branch)
let defaultBranch = execSync('git symbolic-ref refs/remotes/origin/HEAD --short', { cwd: repoCacheDir })
  .toString().trim().replace('origin/', '');
execSync(`git fetch --depth 1 origin ${quoteCustomRef(defaultBranch)}`, { cwd: repoCacheDir });
execSync(`git reset --hard origin/${defaultBranch}`, { cwd: repoCacheDir });

Test plan

  • All 313 existing tests pass (npm run test:install)
  • Manual: install a custom module, push new commits to its main, re-run install, verify cache picks up new commits

@augmentcode
Copy link
Copy Markdown

augmentcode Bot commented Apr 27, 2026

🤖 Augment PR Summary

Summary: Fixes stale updates for shallow-cloned custom modules by resolving and fetching the repository’s default branch explicitly instead of relying on origin/HEAD.

Changes:

  • Adds a quoteCustomRef helper to validate/quote git ref names used in shell commands
  • Extends source parsing to support an optional @<tag-or-branch> suffix and to extract refs from /tree/<ref> (and similar) URL paths
  • Introduces an “effective version” resolution order: --pin override > parsed version > default branch
  • Re-clones cached repositories when the requested version differs from the version recorded in .bmad-source.json
  • Updates the shallow-clone refresh path to fetch and reset against origin/<defaultBranch> explicitly (fallback main)
  • Records clone metadata (version, rawInput, resolved sha) in .bmad-source.json and propagates it into resolved modules for manifest tracking

Technical Notes: Versioned installs now fetch a specific ref and check out FETCH_HEAD; unversioned installs resolve the remote default branch name via git symbolic-ref and then fetch/reset to that branch.

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 3 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread tools/installer/modules/custom-module-manager.js Outdated
Comment thread tools/installer/modules/custom-module-manager.js
Comment thread tools/installer/modules/custom-module-manager.js
@jrevillard jrevillard force-pushed the fix/shallow-clone-update-default-branch branch from b4fd9fb to 613df10 Compare April 27, 2026 06:43
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 27, 2026

📝 Walkthrough

Walkthrough

Enhances custom-module-manager.js to parse version references from user inputs (via @<ref> suffix or extracted from GitHub /tree/<ref>//blob/<ref> URLs), implement conditional cloning/updating logic that re-clones when versions differ, and record resolved metadata in .bmad-source.json for future reference.

Changes

Cohort / File(s) Summary
Ref Parsing & Version Resolution
tools/installer/modules/custom-module-manager.js
Adds parsing of @<tag-or-branch> suffixes and extraction from GitHub URL paths (/tree/<ref>, /blob/<ref>); validates SSH URLs and local paths to prevent misinterpretation; prioritizes explicit @version suffix over URL-extracted refs.
Clone & Pinning Logic
tools/installer/modules/custom-module-manager.js
Implements effectiveVersion computation from options.pinOverride or parsed version; compares against previously cloned version from .bmad-source.json; re-clones repository when requested version differs; uses git clone --branch <ref> when pinned.
Update Logic Differentiation
tools/installer/modules/custom-module-manager.js
Distinguishes between pinned and unpinned modules: pinned modules fetch the requested ref and checkout FETCH_HEAD; unpinned modules resolve the default branch via origin/HEAD, fetch it, and hard-reset to origin/<defaultBranch>.
Metadata Recording & Propagation
tools/installer/modules/custom-module-manager.js
Records resolved sha and version/rawInput in .bmad-source.json; propagates cloneRef, cloneSha, and rawInput into resolved module objects during resolvePlugin; updates spinner text and error messages to include version information.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Parser
    participant Git as Git Operations
    participant FS as File System
    participant Resolver as Module Resolver

    User->>Parser: Provide input (URL or path ± `@ref`)
    Parser->>Parser: Extract/validate ref from suffix or URL path
    Parser->>Git: Determine clone version (pinned vs default)
    
    alt Pinned (explicit version)
        Git->>Git: Clone/fetch with --branch <ref>
        Git->>FS: Checkout FETCH_HEAD
    else Unpinned (default branch)
        Git->>Git: Resolve origin/HEAD
        Git->>Git: Fetch default branch
        Git->>FS: Hard-reset to origin/<branch>
    end
    
    Git->>FS: Record resolved sha & version to .bmad-source.json
    FS->>Resolver: Propagate cloneRef, cloneSha, rawInput
    Resolver->>User: Return resolved module with metadata
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • alexeyv
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: resolve default branch explicitly when updating shallow-cloned custom modules' accurately describes the main change: fixing the update logic for shallow-cloned custom modules by explicitly resolving the default branch instead of relying on stale origin/HEAD.
Description check ✅ Passed The description is directly related to the changeset. It explains the problem (stale origin/HEAD in shallow clones), provides reproduction steps, identifies the root cause, shows the fix with code examples, and includes a test plan.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/shallow-clone-update-default-branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tools/installer/modules/custom-module-manager.js (1)

360-367: Optional: drop the now-redundant generic fetch.

Both branches below (pinned at line 372, unpinned at line 394) issue their own explicit git fetch --depth 1 origin <ref>. The generic git fetch origin --depth 1 at line 363 is no longer needed and just adds an extra network round-trip on every update.

♻️ Suggested simplification
       fetchSpinner.start(`Updating ${displayName}...`);
       try {
-        execSync('git fetch origin --depth 1', {
-          cwd: repoCacheDir,
-          stdio: ['ignore', 'pipe', 'pipe'],
-          env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
-        });
         if (effectiveVersion) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/installer/modules/custom-module-manager.js` around lines 360 - 367,
Remove the redundant generic fetch call that performs execSync('git fetch origin
--depth 1', { cwd: repoCacheDir, ... }) inside the update flow: the
branch-specific fetches later (the pinned and unpinned paths that call 'git
fetch --depth 1 origin <ref>') already fetch the required refs, so delete the
generic fetch block (including the createSpinner/fetchSpinner start immediately
preceding it) to avoid an extra network round-trip; ensure any spinner created
for that generic fetch is removed or reused so there are no orphaned spinner
variables (look for createSpinner, fetchSpinner, and the execSync call using
repoCacheDir).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tools/installer/modules/custom-module-manager.js`:
- Around line 360-367: Remove the redundant generic fetch call that performs
execSync('git fetch origin --depth 1', { cwd: repoCacheDir, ... }) inside the
update flow: the branch-specific fetches later (the pinned and unpinned paths
that call 'git fetch --depth 1 origin <ref>') already fetch the required refs,
so delete the generic fetch block (including the createSpinner/fetchSpinner
start immediately preceding it) to avoid an extra network round-trip; ensure any
spinner created for that generic fetch is removed or reused so there are no
orphaned spinner variables (look for createSpinner, fetchSpinner, and the
execSync call using repoCacheDir).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5ee42720-52c6-41f7-b6d7-37a7fbf0c6ab

📥 Commits

Reviewing files that changed from the base of the PR and between 6ff74ba and b4fd9fb.

📒 Files selected for processing (1)
  • tools/installer/modules/custom-module-manager.js

@jrevillard jrevillard force-pushed the fix/shallow-clone-update-default-branch branch 2 times, most recently from 9443c7f to f9fd7ae Compare April 28, 2026 06:06
…ustom modules

With shallow clones (--depth 1), `origin/HEAD` becomes stale after the
initial clone. The update path used `git reset --hard origin/HEAD` which
never picked up new commits pushed to the default branch.

Resolve the default branch name via `git symbolic-ref refs/remotes/origin/HEAD`,
then fetch and reset against `origin/<branch>` explicitly. Falls back to
`main` if origin/HEAD is not set.
@jrevillard jrevillard force-pushed the fix/shallow-clone-update-default-branch branch from f9fd7ae to 4d39db5 Compare April 29, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant