Skip to content

fix(git-resolver): avoid encoded slash in GitLab tarball URL#11551

Merged
zkochan merged 3 commits into
mainfrom
fix/11533
May 8, 2026
Merged

fix(git-resolver): avoid encoded slash in GitLab tarball URL#11551
zkochan merged 3 commits into
mainfrom
fix/11533

Conversation

@zkochan
Copy link
Copy Markdown
Member

@zkochan zkochan commented May 8, 2026

Summary

Fixes #11533. Installation of dependencies referenced as https://gitlab.com/<user>/<project> (or gitlab:<user>/<project>) was failing in two ways:

  • v11: ERR_PNPM_FETCH_406 from GitLab when fetching the tarball.
  • v10: install completed, but importing the package threw ERR_INVALID_MODULE_SPECIFIER because the virtual store directory contained an encoded slash.

Both stem from the same root cause. @pnpm/hosted-git-info builds GitLab tarball URLs as https://gitlab.com/api/v4/projects/<user>%2F<project>/repository/archive.tar.gz?sha=<commit>. The %2F survives into the virtual-store directory name (depPathToFilename only escapes raw /, not %), and Node refuses any module path with an encoded slash. The same API URL is also intermittently rejected by GitLab with 406.

This change overrides the tarballtemplate for hosted.type === 'gitlab' to the standard https://gitlab.com/<user>/<project>/-/archive/<sha>/<project>-<sha>.tar.gz URL, which works for public and private repos and contains no encoded slashes. The URL still matches the existing isGitHostedPkgUrl check (https://gitlab.com/ + tar.gz), so fetcher selection is unaffected.

Test plan

  • Added a mocked regression test in resolving/git-resolver/test/index.ts that resolves a gitlab: specifier and asserts the new URL shape.
  • Updated existing skipped GitLab tarball assertions and the pick-fetcher URL fixture.
  • Full git-resolver and pick-fetcher test suites pass locally.

Written by an agent (Claude Code, claude-opus-4-7).

Summary by CodeRabbit

  • Bug Fixes

    • Resolved GitLab-hosted dependency installation that could return 406 errors and produce incorrect module naming leading to Node import failures.
  • Tests

    • Added a regression test to ensure GitLab tarball URLs are generated correctly.
  • Chores

    • Updated project metadata and spellcheck allowlist to accommodate the tarball URL template term.

zkochan added 2 commits May 8, 2026 17:22
hosted-git-info's default GitLab tarball URL routes through
`/api/v4/projects/<user>%2F<project>/...`. The `%2F` survives into the
virtual store directory name (depPathToFilename only escapes raw `/`,
not `%`), and Node refuses to import any module whose path contains an
encoded slash. The same URL is also intermittently rejected by GitLab
with a 406.

Override the GitLab tarballtemplate to the `/-/archive/` URL, which works
for both public and private repos and contains no encoded slashes.

Closes #11533
Copilot AI review requested due to automatic review settings May 8, 2026 15:26
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR changes GitLab tarball URL generation to use the repository archive subdirectory format (/‑/archive//...tar.gz), adds a gitlabTarballTemplate helper and integrates it into fromHostedGit, updates tests to validate the new URL form, and adds a cspell allowlist entry for the new field name.

Changes

GitLab Tarball Archive URL Fix

Layer / File(s) Summary
Problem Statement
.changeset/gitlab-tarball-archive-url.md
Changeset documents the patch fix for GitLab 406 responses and Node ERR_INVALID_MODULE_SPECIFIER caused by encoded slashes in repository paths.
Tarball URL Template
resolving/git-resolver/src/parseBareSpecifier.ts
New gitlabTarballTemplate helper constructs GitLab archive URLs from domain, user, project, and committish (encoded or defaulting to HEAD).
GitLab Integration
resolving/git-resolver/src/parseBareSpecifier.ts
fromHostedGit now overrides tarballtemplate with gitlabTarballTemplate for GitLab hosts while preserving the default template for other hosts.
Regression Test
resolving/git-resolver/test/index.ts
New regression test validates /-/archive/<commit>/git-resolver-<commit>.tar.gz format without encoded %2F segments.
Fetcher Test Consistency
fetching/pick-fetcher/test/pickFetcher.ts
GitLab git-resolver URL in the pickFetcher test matrix updated to use the new archive endpoint format.
Spell Checker Config
cspell.json
Added "tarballtemplate" to the allowlist to prevent spell-check warnings.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A rabbit found a tarball trail,
Replaced the encoded %2F pale,
/-/archive/ hops in with cheer,
Dependencies arrive — clear! 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(git-resolver): avoid encoded slash in GitLab tarball URL' accurately summarizes the main change—replacing GitLab API-style tarball URLs containing encoded slashes with direct archive URLs.
Linked Issues check ✅ Passed The PR fully addresses issue #11533 by overriding the tarballtemplate for GitLab hosts to use archive URLs without encoded slashes, fixing both the 406 fetch error and ERR_INVALID_MODULE_SPECIFIER import failures for public and private repos.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the GitLab tarball URL issue: parseBareSpecifier implementation, test coverage for the fix, a spellcheck configuration entry, and test fixture updates—no unrelated modifications present.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/11533

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes GitLab-hosted dependency installs that failed due to %2F (encoded slash) in the GitLab API tarball URL, which both triggered intermittent 406 Not Acceptable responses from GitLab and could produce virtual-store paths that Node rejects (ERR_INVALID_MODULE_SPECIFIER). It does so by overriding the GitLab tarball URL template to use GitLab’s /-/archive/ URL form, and updates relevant tests/fixtures accordingly.

Changes:

  • Override hosted-git-info’s GitLab tarballtemplate to generate https://<domain>/<user>/<project>/-/archive/<sha>/<project>-<sha>.tar.gz URLs.
  • Add a regression test for #11533 asserting the new GitLab tarball URL shape (no %2F).
  • Update pick-fetcher GitLab fixture URL and add tarballtemplate to cspell.json; add a changeset for patch releases.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
resolving/git-resolver/src/parseBareSpecifier.ts Overrides GitLab tarball URL generation to avoid encoded slashes and GitLab API tarball endpoint.
resolving/git-resolver/test/index.ts Adds/updates GitLab tarball URL assertions, including a regression test for #11533.
fetching/pick-fetcher/test/pickFetcher.ts Updates the GitLab tarball URL fixture used to assert git-hosted tarball fetcher selection.
cspell.json Adds tarballtemplate to the dictionary to avoid spellcheck noise.
.changeset/gitlab-tarball-archive-url.md Documents the fix and triggers patch releases for affected packages.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Restore the skipped tests' original API-URL assertions; they document the
old expected shape and weren't running anyway. Add the new `/-/archive/`
URL to the pick-fetcher fixture as an additional case so both shapes are
exercised.
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)
resolving/git-resolver/test/index.ts (1)

376-392: ⚡ Quick win

Cover both GitLab specifier forms in this regression test.

This test currently exercises only the HTTPS GitLab specifier. Since the bug report includes both https://gitlab.com/... and gitlab:..., adding the shorthand form here would close a small coverage gap and guard parser-specific regressions.

Suggested test refinement
-test('resolveFromGit() gitlab tarball uses /-/archive/ URL without encoded slash', async () => {
+test.each([
+  'https://gitlab.com/pnpmjs/git-resolver',
+  'gitlab:pnpmjs/git-resolver',
+])('resolveFromGit() gitlab tarball uses /-/archive/ URL without encoded slash (%s)', async (bareSpecifier) => {
   const headCommit = '988c61e11dc8d9ca0b5580cb15291951812549dc'
   jest.mocked(fetchWithDispatcher).mockImplementation(async (_url, _opts) => {
     return { ok: true } as any // eslint-disable-line `@typescript-eslint/no-explicit-any`
   })
   jest.mocked(git).mockImplementation(async () => ({ stdout: `${headCommit}\tHEAD` }))
-  const resolveResult = await resolveFromGit({ bareSpecifier: 'https://gitlab.com/pnpmjs/git-resolver' })
+  const resolveResult = await resolveFromGit({ bareSpecifier })
   expect(resolveResult).toStrictEqual({
     id: `https://gitlab.com/pnpmjs/git-resolver/-/archive/${headCommit}/git-resolver-${headCommit}.tar.gz`,
     normalizedBareSpecifier: 'gitlab:pnpmjs/git-resolver',
     resolution: {
       tarball: `https://gitlab.com/pnpmjs/git-resolver/-/archive/${headCommit}/git-resolver-${headCommit}.tar.gz`,
       gitHosted: true,
     },
     resolvedVia: 'git-repository',
   })
 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@resolving/git-resolver/test/index.ts` around lines 376 - 392, Add coverage
for the GitLab shorthand form by duplicating or parameterizing the existing test
for resolveFromGit() (test name: "resolveFromGit() gitlab tarball uses
/-/archive/ URL without encoded slash") to also call resolveFromGit with
bareSpecifier: 'gitlab:pnpmjs/git-resolver'; keep the same mocked git (git ->
stdout `${headCommit}\tHEAD`) and fetchWithDispatcher mock, and assert the
expected result object uses id and resolution.tarball
`https://gitlab.com/pnpmjs/git-resolver/-/archive/${headCommit}/git-resolver-${headCommit}.tar.gz`
and normalizedBareSpecifier 'gitlab:pnpmjs/git-resolver' so both the HTTPS and
shorthand forms are validated.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@resolving/git-resolver/test/index.ts`:
- Around line 376-392: Add coverage for the GitLab shorthand form by duplicating
or parameterizing the existing test for resolveFromGit() (test name:
"resolveFromGit() gitlab tarball uses /-/archive/ URL without encoded slash") to
also call resolveFromGit with bareSpecifier: 'gitlab:pnpmjs/git-resolver'; keep
the same mocked git (git -> stdout `${headCommit}\tHEAD`) and
fetchWithDispatcher mock, and assert the expected result object uses id and
resolution.tarball
`https://gitlab.com/pnpmjs/git-resolver/-/archive/${headCommit}/git-resolver-${headCommit}.tar.gz`
and normalizedBareSpecifier 'gitlab:pnpmjs/git-resolver' so both the HTTPS and
shorthand forms are validated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 52616c3a-4437-4423-842f-15d5bda9c6ce

📥 Commits

Reviewing files that changed from the base of the PR and between 0dd7350 and 57e8f80.

📒 Files selected for processing (2)
  • fetching/pick-fetcher/test/pickFetcher.ts
  • resolving/git-resolver/test/index.ts
✅ Files skipped from review due to trivial changes (1)
  • fetching/pick-fetcher/test/pickFetcher.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ubuntu-latest / Node.js 24 / Test
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,tsx,jsx}: Use trailing commas in code following Standard Style with modifications
Prefer functions over classes
Declare functions after they are used, relying on hoisting
Functions should have no more than two or three arguments; use a single options object for functions needing more parameters
Organize imports in order: standard libraries, external dependencies (sorted alphabetically), then relative imports

Files:

  • resolving/git-resolver/test/index.ts

@zkochan zkochan merged commit 39e4266 into main May 8, 2026
11 of 13 checks passed
@zkochan zkochan deleted the fix/11533 branch May 8, 2026 20:29
zkochan added a commit that referenced this pull request May 8, 2026
* fix(git-resolver): avoid encoded slash in GitLab tarball URL

hosted-git-info's default GitLab tarball URL routes through
`/api/v4/projects/<user>%2F<project>/...`. The `%2F` survives into the
virtual store directory name (depPathToFilename only escapes raw `/`,
not `%`), and Node refuses to import any module whose path contains an
encoded slash. The same URL is also intermittently rejected by GitLab
with a 406.

Override the GitLab tarballtemplate to the `/-/archive/` URL, which works
for both public and private repos and contains no encoded slashes.

Closes #11533

* test: avoid cspell-flagged words

* test: keep existing gitlab assertions, only add new ones

Restore the skipped tests' original API-URL assertions; they document the
old expected shape and weren't running anyway. Add the new `/-/archive/`
URL to the pick-fetcher fixture as an additional case so both shapes are
exercised.
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.

Errors when a dependency is specified as https://gitlab.com/owner/repo"

2 participants