Skip to content

Fix vendored Hatch member breaking workspace root (#505) - #1724

Merged
Mikola Lysenko (mikolalysenko) merged 8 commits into
mainfrom
agent/fix-hatch-workspace-member-root-uri
Oct 26, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 8 commits into
mainfrom
agent/fix-hatch-workspace-member-root-uri

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #505

Summary

A vendored scan run from a member of a Hatch 1.16+ workspace wrote six @ {root:uri}/.socket/vendor/... into the member's [project] dependencies and reported success. Every sync of the workspace root env then failed with pip's No connection adapters were found for '{root:uri}/...'. That covered the existing root env, every fresh root env and CI. The member is now refused with pypi_hatch_unsupported before anything is written, and the message points at hosted or agent mode.

Root cause

Hatch's Workspace.get_dependencies reads a member's static [project] dependencies and the optional dependencies of the member's features straight from its pyproject.toml, with no context formatting. {root:uri} is only expanded when the member's own backend or env formats it. socket-patch already refused the equivalent dependency-groups shape but never checked whether the project was consumed by a workspace.

Fix

New utils::hatch_workspace::raw_consumer mirrors Hatch 1.18's Workspace.members / find_members, checked against the installed Hatch source:

  • Where it looks for roots: the member's ancestors up to the checkout root, plus any project in the checkout (gitignore-aware walk, depth ≤ 8). The second part matters because a root can list a sibling through a ../ spec.
  • Where envs come from: hatch.toml envs replaces [tool.hatch.envs]. This now shares one helper with the planner (hatch::environments).
  • Member entries: string, inline-table and array-of-tables entries. {root} and {home} context fields are expanded.
  • Matching: component-wise fnmatch with Python's search semantics, plus exclude. On Windows it folds case and separators the way normcase does.
  • What counts as consumed: once any workspace env lists the member, all of its static dependencies and every optional-dependencies group. Which extras Hatch installs depends on the whole workspace: member features, <member>[extra] references from the root, envs, templates, dependency groups and cross-member extras chains. Three review rounds each found another path a precise model missed, and a miss writes a breaking {root:uri}. A false refusal still leaves hosted and agent mode.
  • Dynamic deps are skipped: Hatch builds those through the backend, which expands {root:uri}.

pypi_hatch::load and preflight call it, so both the real run and --dry-run refuse. A member's own env dependencies, and projects no workspace lists, are still vendored. When an earlier run already vendored the member, the error also says to undo it with socket-patch vendor --revert.

Test evidence

Issue Test Red → green
#505 project.dependencies via glob, path, table, array of tables, {root}, sibling ../ root utils::hatch_workspace::tests::{members_listed_by_glob_path_or_table, sibling_roots_listing_the_member_through_parent_specs} new module
#505 member consumed through features (comment 2) optional_dependencies_follow_member_features, vendor::pypi_hatch::tests::member_consumed_by_a_hatch_workspace_is_refused red without the require_no_raw_workspace call (panics "six must be refused"), green with it
#505 workspace declared in hatch.toml (comment 2) hatch_toml_workspaces_replace_pyproject_envs, same pypi_hatch test same
#505 end to end, real Hatch 1.18.1 e2e_vex_build hatch::hatch_workspace_member_vendored_is_refused: vendored scan from the member exits non-zero with pypi_hatch_unsupported, both pyprojects untouched, root hatch env create still succeeds pass

Commands run locally:

  • cargo fmt --all -- --check: ok

  • cargo clippy --workspace --all-features -- -D warnings: ok

  • SOCKET_PATCH_HATCH_E2E_REQUIRED=1 SOCKET_PATCH_HATCH_E2E_VERSION=1.18.1 cargo test -p socket-patch-cli --all-features --test e2e_vex_build -- hatch:: --ignored: 7/7 pass

  • cargo test --workspace --all-features --no-fail-fast: everything passes except 11 tests that fail the same way in this sandbox and are unrelated to this PR:

    • permission-based write and remove failures, because the container runs as uid 0, e.g. pypi_poetry::wire_write_failure_* and copy_tree::relax_loop_*
    • two e2e_bun_lockb cases that need patches-api.socket.dev, which the proxy blocks

    CI is the authority for those.

Review

I ran /code-review high and fixed every confirmed finding in ce0ef59: sibling roots, array-of-tables members, {root} specs, the stop at the checkout root, linear-time fnmatch, Windows normcase, exact feature keys, the revert hint and the shared env-table helper. I didn't change the self-extras finding: Hatch's resolve_extras does expand self-referencing extras ("Self-referencing extras within each project AND cross-project extras are expanded in a single BFS pass"), so following them is correct. Bugbot then raised 4 findings over two rounds. I fixed three in 40c3024 and c37fc34: FIFO-safe reads, bounding the walk with the shared repo lookup (home dir and `GIT_CEILING_DIRECTORIES`), and matching members reached through symlinks. I answered the Windows-case finding with evidence from Hatch's `find_members`. Bugbot's latest review of c37fc34 found no issues.

I also left the second ancestor walk on the supersede path: it's a few small TOML reads per package.

Known limits

  • A member spec that uses other context fields ({env:..}) isn't resolved and fails open.
  • Workspace roots outside the git checkout, or more than 8 levels deep, aren't searched.
  • The check doesn't look at the installed Hatch version. On Hatch < 1.16, where workspace tables are ignored, a member is still refused. That's conservative, and hosted or agent mode work there.

No wrapper (npm/pypi/gem) changes are needed: this is CLI-internal behavior.

https://claude.ai/code/session_01Bh7BpCB27Q7GVBEkuSCuS6


Note

Medium Risk
Changes PyPI Hatch vendoring preflight/load gates and adds filesystem/TOML workspace detection; false positives would block legitimate vendored member scans, but behavior is intentionally conservative with clear escape via hosted/agent mode.

Overview
Vendored Hatch scans from a workspace member no longer write {root:uri} into the member's pyproject.toml, which would break every workspace-root environment sync (#505). load and preflight now call require_no_raw_workspace, which uses new hatch_workspace::raw_consumer to detect when a Hatch 1.16+ workspace environment lists the project and would install a static [project] dependency or optional dependency without expanding {root:uri}. Those runs fail early with pypi_hatch_unsupported (hosted/agent unchanged).

raw_consumer walks ancestor and sibling workspace roots (git checkout, member globs/paths, hatch.toml vs pyproject env tables, symlinks, excludes) and reuses hatch::environments. Listed members are treated as consuming all optional dependencies conservatively. Unit tests, a pypi_hatch refusal test, an ignored e2e case, and docs/testing/hatch.md document the behavior.

Reviewed by Cursor Bugbot for commit 5a83ffc. Configure here.


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
A vendored scan run from a member of a Hatch 1.16+ workspace wrote
{root:uri} references into the member's [project] dependencies and
reported success. Hatch's workspace environment reads a member's
static dependencies raw, so every sync of the workspace root env then
failed with pip's "No connection adapters were found for
'{root:uri}/...'" until someone hand-edited the member.

Detect a workspace environment declared in an ancestor pyproject.toml
or hatch.toml (up to the checkout root) that lists the project, the
way Hatch resolves members (globs, path tables, exclude, features and
self-referencing extras), and refuse the package with
pypi_hatch_unsupported before anything is written, pointing at hosted
or agent mode. Dependencies the workspace does not read raw (the
member's own env deps, unselected extras, dynamic metadata) are still
vendored.

Fixes #505

Assisted-by: Claude Code:claude-opus-5-5
Review follow-ups for the workspace-member refusal:

- look for a workspace root anywhere in the checkout, since Hatch
  lets a root list a member beside it through a ../ spec
- read members written as an array of tables and specs using the
  {root} and {home} context fields
- stop at the checkout root even when the member is that root
- match fnmatch patterns in linear time, and fold case and
  separators on Windows the way Python's fnmatch does
- look a selected feature up by its exact key, as Hatch does
- tell a user whose member was vendored by an earlier run how to
  undo it
- share the hatch.toml/pyproject env-table rule with the planner

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 25, 2026 17:26
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread crates/socket-patch-core/src/utils/hatch_workspace.rs
Comment thread crates/socket-patch-core/src/utils/hatch_workspace.rs Outdated
Comment thread crates/socket-patch-core/src/utils/hatch_workspace.rs
Read workspace candidates through the non-blocking regular-file
reader, so a FIFO at a pyproject.toml or hatch.toml path can't hang a
vendored scan, and walk ancestors with the shared repository lookup,
which stops at the checkout root, never enters the home directory and
honours GIT_CEILING_DIRECTORIES.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread crates/socket-patch-core/src/utils/hatch_workspace.rs Outdated
Resolve each workspace member spec by listing directories the way
Hatch's find_members does, following symlinked directories, and
compare what it finds with the scanned project by real path. Roots are
looked for above both the spelled and the real path, so a member Hatch
reaches through a symlink is refused whichever path the scan was given.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 25, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Labeled Ready for review by the burn-down agent.

  • Head: c37fc342ba91b15b83f066e7b18c88ceb61627e4
  • CI: 56/56 checks settled green on this head (37 success, 19 skipped); mergeable, no conflicts
  • Bugbot: reviewed c37fc34, no new issues. Its 4 earlier findings are resolved: 3 fixed in 40c3024/c37fc34 (FIFO-safe reads, bounded workspace walk, symlinked members), 1 refuted with Hatch find_members evidence (Windows case)
  • Reviewer focus: utils/hatch_workspace.rs (new 665-line mirror of Hatch's workspace member matching) and the conservative refusal on Hatch < 1.16 noted under Known limits
  • Slack announcement not sent this run (the Slack send action wasn't available); the next run will retry

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review brief

What it does: A vendored scan run inside a member of a Hatch 1.16+ workspace used to write {root:uri}/.socket/vendor/... into the member's [project] dependencies. Hatch reads those strings raw, without expanding {root:uri}, so every sync of the workspace root env failed. Vendored mode now detects a workspace env that consumes the member that way and refuses with pypi_hatch_unsupported before writing anything. The error points at hosted or agent mode, and at vendor --revert if the member was already vendored.

Risk: low to medium. It only adds a refusal on the vendored Hatch path, so the worst case is a false refusal, not a broken install. The new code is a 665-line module that reimplements Hatch's member matching (fnmatch, {root}/{home} specs, exclude, features, self-extras). Each vendored package also triggers a gitignore-aware walk of the checkout (depth ≤ 8). That is fine for a CLI command, but it is the part to watch in large monorepos.

Look here:

Verified:

  • Read the full diff. CHANGELOG.md is untouched. No debug leftovers and no unrelated edits.
  • cargo test -p socket-patch-core --lib -- hatch: 72/72 pass.
  • With the two require_no_raw_workspace calls removed, member_consumed_by_a_hatch_workspace_is_refused fails ("six must be refused"), so the test does cover the fix.
  • CI: 37 success, 19 skipped, 0 failed on c37fc34 (ci-ok and clippy green). Cursor Bugbot: success, and all 4 review threads are resolved.

Changes I made: none.

Open questions: none blocking. One small nit: docs/testing/hatch.md says roots are looked for "in an ancestor", but the code also finds sibling roots in the checkout that list the member through ../.

Auto-merge is armed, so approving sends it straight to the merge queue.


Generated by Claude Code

Comment thread crates/socket-patch-core/src/utils/hatch_workspace.rs Outdated
Hatch's all_dependencies_complex expands a workspace member's raw
optional dependencies whenever any of the environment's requirement
strings names the member with an extra: the root's own dependencies,
its environments', or another member's (lib[s3]). Those groups reach
pip raw, so a vendored {root:uri} there breaks the workspace just
like a selected feature does. Count those extras as consumed.

Also say in the Hatch notes that a sibling root listing the member
through ../ is found as well.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread crates/socket-patch-core/src/utils/hatch_workspace.rs
Comment thread crates/socket-patch-core/src/utils/hatch_workspace.rs Outdated
Collect the requirement strings that name the member from what the
workspace environment actually installs: the root's dependencies and
its environment features' extras, the environment's and its
templates' dependencies (from hatch.toml when that replaces the
pyproject tables), and the other members' dependencies and selected
features. Resolve the extras they name through the same normalized
lookup Hatch's resolve_extras uses, so a mixed-case extra name is
found and strings Hatch never installs select nothing.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[final reviewer] Disabled auto-merge. This PR was armed only on the strength of the final-review brief, and the head has gained non-merge commits since that brief (review fixes pushed today). I'll write a fresh brief once those pushes settle and CI and Bugbot are green on the new head, then re-arm. The Ready for review label is still on; the burn-down should confirm it against the new head.


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Cross-package extras stay unexpanded
    • Implemented resolve_extras_across_workspace to expand extras from all packages (root and siblings) before filtering, matching Hatch's resolve_extras behavior that walks every install spec transitively.

Create PR

Or push these changes by commenting:

@cursor push 90d6630253
Preview (90d6630253)
diff --git a/crates/socket-patch-core/src/utils/hatch_workspace.rs b/crates/socket-patch-core/src/utils/hatch_workspace.rs
--- a/crates/socket-patch-core/src/utils/hatch_workspace.rs
+++ b/crates/socket-patch-core/src/utils/hatch_workspace.rs
@@ -153,6 +153,92 @@
     Some((path, features))
 }
 
+/// Resolve extras across the workspace: expand extras from specs that
+/// reference the root or siblings, then filter to specs that reference `own`.
+/// This matches Hatch's `resolve_extras` behavior, which walks every install
+/// spec against every member's and the root's `optional-dependencies`.
+fn resolve_extras_across_workspace(
+    specs: &[String],
+    root_project: Option<&Item>,
+    sibling_projects: &[&Item],
+    own: &str,
+) -> Vec<String> {
+    // Build a map of package name -> optional-dependencies
+    let mut extras_map: BTreeMap<String, BTreeMap<String, Vec<String>>> = BTreeMap::new();
+
+    if let Some(root) = root_project {
+        if let Some(name) = root.get("name").and_then(Item::as_str) {
+            let canonical_name = canonicalize_pypi_name(name);
+            if let Some(groups) = root
+                .get("optional-dependencies")
+                .and_then(Item::as_table_like)
+            {
+                let mut pkg_extras = BTreeMap::new();
+                for (key, group) in groups.iter() {
+                    let deps: Vec<String> = strings(Some(group))
+                        .into_iter()
+                        .map(str::to_owned)
+                        .collect();
+                    pkg_extras.insert(key.to_owned(), deps.clone());
+                    pkg_extras.insert(canonicalize_pypi_name(key), deps);
+                }
+                extras_map.insert(canonical_name, pkg_extras);
+            }
+        }
+    }
+
+    for sibling in sibling_projects {
+        if let Some(name) = sibling.get("name").and_then(Item::as_str) {
+            let canonical_name = canonicalize_pypi_name(name);
+            if let Some(groups) = sibling
+                .get("optional-dependencies")
+                .and_then(Item::as_table_like)
+            {
+                let mut pkg_extras = BTreeMap::new();
+                for (key, group) in groups.iter() {
+                    let deps: Vec<String> = strings(Some(group))
+                        .into_iter()
+                        .map(str::to_owned)
+                        .collect();
+                    pkg_extras.insert(key.to_owned(), deps.clone());
+                    pkg_extras.insert(canonicalize_pypi_name(key), deps);
+                }
+                extras_map.insert(canonical_name, pkg_extras);
+            }
+        }
+    }
+
+    // Expand all specs with extras
+    let mut expanded = Vec::new();
+    let mut queue: VecDeque<String> = specs.iter().cloned().collect();
+    let mut seen = BTreeSet::new();
+
+    while let Some(spec) = queue.pop_front() {
+        if !seen.insert(spec.clone()) {
+            continue;
+        }
+
+        let name = canonicalize_pypi_name(pep508_name(&spec));
+        if name == own {
+            expanded.push(spec.clone());
+        }
+
+        // If this spec has extras and references a package we know about, expand them
+        let spec_extras = extras(&spec);
+        if !spec_extras.is_empty() {
+            if let Some(pkg_extras) = extras_map.get(&name) {
+                for extra in spec_extras {
+                    if let Some(deps) = pkg_extras.get(&extra) {
+                        queue.extend(deps.iter().cloned());
+                    }
+                }
+            }
+        }
+    }
+
+    expanded
+}
+
 /// For each environment of the project at `root` whose workspace lists
 /// `member`: the environment name and the member's selected features
 /// (normalized): those the entry selects, plus every extra of `own` (the
@@ -212,7 +298,8 @@
             // templates') dependencies, and what the workspace takes from
             // the other members.
             let mut specs = environment_specs(envs, env);
-            if let Some(root_project) = project.as_ref().and_then(|doc| doc.get("project")) {
+            let root_project_item = project.as_ref().and_then(|doc| doc.get("project"));
+            if let Some(root_project) = root_project_item {
                 let env_features: Vec<String> = environment_chain(envs, env)
                     .iter()
                     .flat_map(|config| strings(config.get("features")))
@@ -220,22 +307,26 @@
                     .collect();
                 specs.extend(selected_specs(root_project, &env_features));
             }
+            let mut sibling_docs = Vec::new();
             for (paths, features) in &listed {
                 for path in paths
                     .iter()
                     .filter(|path| real(path).as_deref() != Some(member))
                 {
                     if let Some(sibling) = parse(path, HATCH_FILES[0]) {
-                        if let Some(sibling) = sibling.get("project") {
-                            specs.extend(selected_specs(sibling, features));
+                        if let Some(sibling_project) = sibling.get("project") {
+                            specs.extend(selected_specs(sibling_project, features));
+                            sibling_docs.push(sibling);
                         }
                     }
                 }
             }
-            let referenced = specs
-                .into_iter()
-                .filter(|spec| canonicalize_pypi_name(pep508_name(spec)) == own)
+            let sibling_projects: Vec<&Item> = sibling_docs
+                .iter()
+                .filter_map(|doc| doc.get("project"))
                 .collect();
+            let referenced =
+                resolve_extras_across_workspace(&specs, root_project_item, &sibling_projects, &own);
             found.push((env.to_string(), features.clone(), referenced));
         }
     }

You can send follow-ups to the cloud agent here.

Comment thread crates/socket-patch-core/src/utils/hatch_workspace.rs Outdated
Comment thread crates/socket-patch-core/src/utils/hatch_workspace.rs Outdated
Comment thread crates/socket-patch-core/src/utils/hatch_workspace.rs Outdated
Which optional dependencies a Hatch workspace environment installs from
a member depends on the whole workspace: the member's features,
<member>[extra] references from the root, environments, their
templates and dependency groups, and extras of other members that
reach it. Each review round found another path the precise model
missed, and a miss writes a {root:uri} that breaks every workspace
sync.

Once a workspace lists the member, count all its static dependencies
and optional dependencies as read raw instead. A refusal still leaves
hosted and agent mode, and the module shrinks to member discovery.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 5a83ffc. Configure here.

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: Ready for review at 5a83ffc.

  • CI: 56/56 checks on 5a83ffc passed or skipped (37 success, 19 skipped); mergeable, no conflicts. No main-wide failures. CHANGELOG.md untouched.
  • Bugbot: reviewed 5a83ffc, no findings; all review threads resolved.
  • Re-confirmed at the new head (previous label comment was at c37fc34). Auto-merge was disabled by the final reviewer pending a fresh brief.
  • Slack announcement not sent this run (Slack send unavailable); the next run will retry.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review brief

What it does: Vendored mode now refuses (pypi_hatch_unsupported) to wire a package into a Hatch project that a Hatch workspace environment lists as a member when the package is one of the member's [project] dependencies or extras. Hatch reads those raw, so the {root:uri} that vendoring writes would break the workspace root's syncs (#505). It only adds a refusal and writes nothing. Hosted and agent modes are unchanged.

Risk: low. The worst case of a miss is what main does today. The new code is about 600 lines that emulate Hatch's find_members globbing and walk the checkout for sibling roots. Where it gets that wrong, it fails open. It treats every extra as raw, which fails closed and is documented.

Look here

  • pypi_hatch.rs:45, :121: the check is called in load and preflight.
  • pypi_hatch.rs:143-177: require_no_raw_workspace and the refusal text.
  • hatch_workspace.rs:49-84:`` raw_consumer, which compares the member's canonical and as-spelled paths.
  • hatch_workspace.rs:98-142:``` root discovery (ancestors, plus a gitignore-aware checkout walk to depth 8).
  • hatch_workspace.rs:223-284:```` members_of, the glob and `exclude` emulation.

Verified

  • A subagent read the full diff. cargo test -p socket-patch-core --lib -- hatch: 72/72 passed.
  • With both require_no_raw_workspace calls stubbed out, member_consumed_by_a_hatch_workspace_is_refused fails, so the test covers the change.
  • CI is green on 5a83ffc (ci-ok and clippy). Bugbot is clean and there are no open threads. CHANGELOG.md is untouched.
  • Not run: the #[ignore] e2e test, which needs real Hatch 1.16+.

Changes I made: none.

Open questions (none blocking)

  • On Windows, sibling roots that use ../ member specs are probably missed: the checkout path is canonicalized to a verbatim \\?\ path, and join("../..") does not resolve against it. That fails open, the same as before this PR. Ancestor roots are still found. I couldn't check this on Windows.
  • The unit test's not-refused branch never runs, because every case lists the same refused names. A follow-up could add a package the member doesn't declare.

Auto-merge is armed, so approving sends this straight to the merge queue.


Generated by Claude Code

Merged via the queue into main with commit 22162d7 Oct 26, 2026
56 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-hatch-workspace-member-root-uri branch October 26, 2026 18:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vendored scan in a Hatch workspace member writes {root:uri} that the workspace root env never expands, so every hatch run at the root fails to install

3 participants