diff --git a/BUILD.bazel b/BUILD.bazel index 8f2b850b18..137b32c871 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -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"}, ) diff --git a/MODULE.bazel b/MODULE.bazel index 2598945a00..6451f99f99 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -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") diff --git a/devinfra/claude_hooks/README.md b/devinfra/claude_hooks/README.md index 72d76fa6b5..dee3a97f99 100644 --- a/devinfra/claude_hooks/README.md +++ b/devinfra/claude_hooks/README.md @@ -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 diff --git a/devinfra/claude_hooks/config/bazelrc.mako b/devinfra/claude_hooks/config/bazelrc.mako index ae54673680..a9c1af03bd 100644 --- a/devinfra/claude_hooks/config/bazelrc.mako +++ b/devinfra/claude_hooks/config/bazelrc.mako @@ -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 diff --git a/patches/rules_python_lock_inherit_action_env.patch b/patches/rules_python_lock_inherit_action_env.patch new file mode 100644 index 0000000000..4ec7b279f6 --- /dev/null +++ b/patches/rules_python_lock_inherit_action_env.patch @@ -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], diff --git a/requirements_bazel.txt b/requirements_bazel.txt index 7e55dc0852..722c7fc78c 100644 --- a/requirements_bazel.txt +++ b/requirements_bazel.txt @@ -349,10 +349,6 @@ aw-core==0.5.13 \ # via # ducktape (pyproject.toml) # aw-client -backports-tarfile==1.2.0 \ - --hash=sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34 \ - --hash=sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991 - # via jaraco-context bcrypt==5.0.0 \ --hash=sha256:046ad6db88edb3c5ece4369af997938fb1c19d6a699b9c1b27b0db432faae4c4 \ --hash=sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a \ @@ -1742,7 +1738,6 @@ importlib-metadata==8.7.1 \ --hash=sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb \ --hash=sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151 # via - # keyring # litellm # opentelemetry-api importlib-resources==6.5.2 \ @@ -2860,10 +2855,6 @@ opentelemetry-semantic-conventions==0.61b0 \ --hash=sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a \ --hash=sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2 # via opentelemetry-sdk -overrides==7.7.0 \ - --hash=sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a \ - --hash=sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49 - # via jupyter-server packaging==26.0 \ --hash=sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4 \ --hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529 @@ -5241,20 +5232,16 @@ typing-extensions==4.15.0 \ --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 # via # ducktape (pyproject.toml) - # aiosignal # alembic # anthropic - # anyio # asyncpg-stubs # aw-client # beautifulsoup4 # cattrs - # exceptiongroup # fastapi # flexcache # flexparser # huggingface-hub - # ipython # jupyter-kernel-client # jupyter-mimetypes # mcp @@ -5265,17 +5252,12 @@ typing-extensions==4.15.0 \ # opentelemetry-sdk # opentelemetry-semantic-conventions # pint - # psycopg # py-key-value-aio # pydantic # pydantic-core # pyee # pygithub - # pyopenssl - # pytest-asyncio - # referencing # sqlalchemy - # starlette # testcontainers # typing-inspection typing-inspection==0.4.2 \