Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,25 @@ js_library(
)

# Generate requirements_bazel.txt from pyproject.toml
# Run: bazel run //:requirements.update
# Run: bazel run //:requirements.run # Direct uv invocation, uses shell proxy env vars
# Run: bazel run //:requirements.update # Via build action (sandbox), needs proxy env passed
#
# IMPORTANT: This target requires direct network access to PyPI for dependency resolution.
# dockerNetwork=bridge gives the RBE action outbound internet access (off by default).
# - RBE: exec_properties dockerNetwork=bridge gives outbound internet access.
# - Local (Claude Code containers): proxy vars (https_proxy, SSL_CERT_FILE) are passed
# via --action_env in .bazelrc. UV_NATIVE_TLS=1 makes uv use OpenSSL (reads SSL_CERT_FILE)
# rather than bundled rustls (which ignores SSL_CERT_FILE). ~/.bazelrc adds a
# --sandbox_add_mount_pair for /root/.claude so the session cert is accessible.
lock(
name = "requirements",
srcs = ["pyproject.toml"],
out = "requirements_bazel.txt",
env = {
# Use OpenSSL TLS stack so SSL_CERT_FILE (passed via --action_env) is respected.
# Without this, uv uses bundled rustls which ignores SSL_CERT_FILE, so the proxy
# CA cert is not trusted and HTTPS connections through TLS-inspecting proxies fail.
"UV_NATIVE_TLS": "1",
},
exec_properties = {"dockerNetwork": "bridge"},
)

Expand Down
15 changes: 15 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ bazel_dep(name = "platforms", version = "1.0.0")
bazel_dep(name = "bazel_skylib", version = "1.9.0")
bazel_dep(name = "rules_python", version = "1.8.3")
bazel_dep(name = "rules_python_gazelle_plugin", version = "1.8.3")

# Patch: make the uv lock() rule inherit --action_env values so proxy env vars
# (https_proxy, SSL_CERT_FILE, etc.) reach the uv pip compile sandbox action.
# Without this, ctx.actions.run_shell(env = ctx.attr.env) ignores --action_env,
# and //:requirements_test fails in Claude Code containers where DNS is blocked.
# TODO: remove once https://github.com/bazelbuild/rules_python/issues/XXXX is fixed upstream.
single_version_override(
module_name = "rules_python",
patch_strip = 1,
patches = [
"//patches:rules_python_lock_inherit_action_env.patch",
],
version = "1.8.3",
)

bazel_dep(name = "gazelle", version = "0.47.0", repo_name = "bazel_gazelle")
bazel_dep(name = "rules_go", version = "0.60.0", repo_name = "io_bazel_rules_go")
bazel_dep(name = "protobuf", version = "33.4")
Expand Down
40 changes: 15 additions & 25 deletions devinfra/claude_hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,36 +272,26 @@ Setup files (in `~/.cache/claude-hooks/auth-proxy/`, created by `proxy_setup.py`

## Known Limitations

### rules_python lock() doesn't inherit --action_env
### rules_python lock() --action_env inheritance (patched)

The `lock()` rule from `@rules_python//python/uv:lock.bzl` has a bug/limitation: it doesn't inherit `--action_env` values because it sets an explicit `env` attribute on `ctx.actions.run_shell()`.
The `lock()` rule from `@rules_python//python/uv:lock.bzl` had a bug where it
didn't inherit `--action_env` values because `ctx.actions.run_shell()` used
`env = ctx.attr.env`, which ignores `ctx.configuration.default_shell_env`.

**Impact**: The `uv pip compile` sandbox action doesn't receive proxy environment variables set via `--action_env=HTTPS_PROXY=...`.
**This is fixed** by `patches/rules_python_lock_inherit_action_env.patch`, applied
via `single_version_override` in `MODULE.bazel`. The patch changes the rule to
`env = dicts.add(ctx.configuration.default_shell_env, ctx.attr.env)`, so
`--action_env` values are inherited while explicit lock rule env still takes precedence.

**Workaround**: Pass proxy env vars directly to the `lock()` rule's `env` attribute:
**How //:requirements works in Claude Code sessions** (see `config/bazelrc.mako`):

```starlark
lock(
name = "requirements",
srcs = [...],
out = "requirements_bazel.txt",
env = {
"HTTPS_PROXY": "http://localhost:18081",
"SSL_CERT_FILE": "/path/to/combined_ca.pem", # For TLS inspection
},
)
```

**Root cause**: In `python/uv/private/lock.bzl`:

```starlark
ctx.actions.run_shell(
...
env = ctx.attr.env, # <-- Explicit env overrides --action_env inheritance
)
```
- `--strategy=PyRequirementsLockUv=local` forces the action to run locally (not RBE)
- `--action_env=https_proxy=http://127.0.0.1:18081` routes uv through the auth proxy
- `--action_env=SSL_CERT_FILE=...combined_ca.pem` trusts the proxy's CA
- `UV_NATIVE_TLS=1` (in the lock rule's `env`) makes uv use OpenSSL (respects SSL_CERT_FILE)

This should arguably use `dicts.add(ctx.configuration.default_shell_env, ctx.attr.env)` to merge `--action_env` with rule-specific env.
In CI/RBE (no session bazelrc): the lock rule uses `exec_properties dockerNetwork:bridge`
for direct internet access without needing a proxy.

### 9p filesystem doesn't support Unix socket hard links

Expand Down
19 changes: 19 additions & 0 deletions devinfra/claude_hooks/config/bazelrc.mako
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,25 @@ common --repo_env=GOSUMDB=sum.golang.org
common --repo_env=GIT_SSL_CAINFO=${combined_ca_path | sh}
common --repo_env=SSL_CERT_FILE=${combined_ca_path | sh}

# Force PyRequirementsLockUv (uv pip compile) to run locally, not on RBE.
# This ensures the action can reach the auth proxy at localhost:18081.
# In CI/RBE, the lock rule uses exec_properties dockerNetwork:bridge instead.
build --strategy=PyRequirementsLockUv=local

# Pass proxy and TLS CA into local build sandbox actions.
# Required for //:requirements (uv pip compile) which runs locally (see above).
# rules_python's lock() rule inherits these via ctx.configuration.default_shell_env
# after our patch (patches/rules_python_lock_inherit_action_env.patch).
# Explicit values (not inherited from server env) so the proxy URL doesn't vary
# with JWT rotation in the Bazel server environment.
build --action_env=http_proxy=http://127.0.0.1:${proxy_port}
build --action_env=https_proxy=http://127.0.0.1:${proxy_port}
build --action_env=HTTP_PROXY=http://127.0.0.1:${proxy_port}
build --action_env=HTTPS_PROXY=http://127.0.0.1:${proxy_port}
build --action_env=SSL_CERT_FILE=${combined_ca_path | sh}
build --action_env=REQUESTS_CA_BUNDLE=${combined_ca_path | sh}
build --action_env=CURL_CA_BUNDLE=${combined_ca_path | sh}

# Tag invocations for BuildBuddy filtering
build --build_metadata=ROLE=claude-code

Expand Down
27 changes: 27 additions & 0 deletions patches/rules_python_lock_inherit_action_env.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--- a/python/uv/private/lock.bzl 2026-03-09 12:19:18.662832117 +0000
+++ b/python/uv/private/lock.bzl 2026-03-09 12:19:18.669196605 +0000
@@ -15,6 +15,7 @@
"""An implementation for a simple macro to lock the requirements.
"""

+load("@bazel_skylib//lib:dicts.bzl", "dicts")
load("@bazel_skylib//lib:shell.bzl", "shell")
load("//python:py_binary.bzl", "py_binary")
load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED") # buildifier: disable=bzl-visibility
@@ -143,14 +144,14 @@
python_files,
],
progress_message = "Creating a requirements.txt with uv: %{label}",
- env = ctx.attr.env,
+ env = dicts.add(ctx.configuration.default_shell_env, ctx.attr.env),
)

return [
DefaultInfo(files = depset([output])),
_RunLockInfo(
args = args.run_info,
- env = ctx.attr.env,
+ env = dicts.add(ctx.configuration.default_shell_env, ctx.attr.env),
srcs = depset(
srcs + [uv],
transitive = [python_files],
18 changes: 0 additions & 18 deletions requirements_bazel.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading