Skip to content

Git credentials in workspace member [tool.uv.sources] not applied under uv sync --frozen #19419

@0x2b3bfa0

Description

@0x2b3bfa0

Summary

When uv sync --frozen is run against a workspace, credentials embedded in
[tool.uv.sources] entries that live in workspace-member pyproject.toml
files are silently dropped. The same entries work correctly under uv lock
and uv sync (without --frozen). For private git deps this manifests as
fatal: could not read Username for 'https://github.com': terminal prompts disabled.

Minimal reproducible example

repo/
├── pyproject.toml              # workspace root; declares members; no [tool.uv.sources]
├── uv.lock
└── nested/
    └── pyproject.toml          # [tool.uv.sources]
                                # example = { git = "https://user:password@github.com/organization/repository", tag = "v1" }

Root pyproject.toml:

[project]
name = "root"
version = "0"
requires-python = ">=3.12"
dependencies = ["example"]

[tool.uv.workspace]
members = ["nested"]

nested/pyproject.toml:

[project]
name = "nested"
version = "0"
requires-python = ">=3.12"
dependencies = ["example"]

[tool.uv.sources]
example = { git = "https://user:password@github.com/organization/repository", tag = "v1" }

Steps:

  1. uv lock from the repo root — succeeds; lockfile correctly records
    git+https://github.com/organization/repository@<commit> with no credentials (per
    the redaction behavior introduced in Redact registry credentials in lockfile #5803).

  2. uv sync --frozen from the repo root — fails:

    × Failed to download and build `example @ git+https://github.com/organization/repository@<commit>`
    ├─▶ Git operation failed
    ├─▶ failed to clone into: /root/.cache/uv/git-v0/db/...
    ├─▶ failed to fetch commit `<commit>`
    ╰─▶ process didn't exit successfully:
        `/usr/bin/git fetch --force --update-head-ok 'https://github.com/organization/repository' ...`
        (exit status: 128)
        --- stderr
        fatal: could not read Username for 'https://github.com': terminal prompts disabled
    

Workaround: move the credentialed [tool.uv.sources] entry into the
workspace-root pyproject.toml, or configure git directly via
git config --global url.<auth>.insteadOf <bare> / GIT_ASKPASS / .netrc.

Root cause

Under --frozen, project discovery is intentionally scoped to the root only:

VirtualProject::discover(
project_dir,
&DiscoveryOptions {
members: MemberDiscovery::None,
..DiscoveryOptions::default()
},
workspace_cache,
)
.await?
} else if let [name] = package.as_slice() {

store_credentials_from_target then iterates target.sources(), which walks
workspace.sources() plus each loaded member's [tool.uv.sources]:

pub(crate) fn sources(&self) -> impl Iterator<Item = &Source> {
match self {
Self::Project { workspace, .. }
| Self::Projects { workspace, .. }
| Self::Workspace { workspace, .. }
| Self::NonProjectWorkspace { workspace, .. } => {
Either::Left(workspace.sources().values().flat_map(Sources::iter).chain(
workspace.packages().values().flat_map(|member| {
member
.pyproject_toml()
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.sources.as_ref())
.map(ToolUvSources::inner)
.into_iter()
.flat_map(|sources| sources.values().flat_map(Sources::iter))
}),
))
}
Self::Script { script, .. } => {
Either::Right(script.sources().values().flat_map(Sources::iter))
}
}
}

With MemberDiscovery::None, members aren't loaded, so member-level
[tool.uv.sources] are invisible to store_credentials_from_target, and
GIT_STORE never gets the credentials. GitSource::fetch then runs against
the bare URL:

let ident = cache_digest(self.git.repository());
let db_path = self.cache.join("db").join(&ident);
// Authenticate the URL, if necessary.
let remote = if let Some(credentials) = GIT_STORE.get(self.git.repository()) {
Cow::Owned(credentials.apply(self.git.url().clone()))
} else {

Expected behavior

uv sync --frozen should respect credentials defined in any workspace
member's [tool.uv.sources], consistently with uv lock and uv sync
without --frozen. The lockfile already records workspace membership, so
credentials can be collected from each member's pyproject.toml on disk
without doing full member discovery / validation.

Related

Platform

macOS 26 arm64

Version

uv 0.11.14 (3fdfdc7 2026-05-12 aarch64-apple-darwin)

Python version

Python 3.14.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions