Skip to content

JupyterHub on GCP: transparent Cloud Build images + run-scoped Dask Gateway clusters (LCR-174)#163

Merged
aboucaud merged 8 commits into
mainfrom
jupyterhub-gateway-cloudbuild
Jul 24, 2026
Merged

JupyterHub on GCP: transparent Cloud Build images + run-scoped Dask Gateway clusters (LCR-174)#163
aboucaud merged 8 commits into
mainfrom
jupyterhub-gateway-cloudbuild

Conversation

@EiffL

@EiffL EiffL commented Jul 22, 2026

Copy link
Copy Markdown
Member

Summary

Implements the Kubernetes/JupyterHub-on-GCP PRD (LCR-174) against the hub-deploy dask-cloudbuild deployment: on a lightcone hub, lc run makes sure the project image is up to date (building through GCP Cloud Build when needed), creates a run-scoped Dask Gateway cluster with that image, runs the pipeline in worker pods, and culls the cluster when the run finishes — the exact same zero-configuration UX as on a laptop or a SLURM allocation. Everything keys off the env contract the deployment injects into user pods (DASK_GATEWAY__*, LIGHTCONE_REGISTRY, LIGHTCONE_BUILD_BUCKET, LIGHTCONE_BUILD_SERVICE_ACCOUNT); nothing is configured by the user.

Design

One new runtime, not a new subsystem. The spec already routes everything through two seams — the container runtime (wrap_recipe, image resolution) and the cluster context (cluster_for_run) — so the k8s integration is a value flowing through existing seams:

  • kubernetes runtime (engine/container.py): the worker pod is the container. wrap_recipe is a passthrough (no nested containers), and Containerfile specs resolve to registry refs. One content-addressed identity everywhere: image_identity() yields the digest that spells lc-<project>-<hash> in a local store and $LIGHTCONE_REGISTRY/lc-<project>:<hash> in the registry — so code_version, staleness, and reruns work identically on every backend.
  • Build backend (engine/cloudbuild.py, ~400 lines): selected when the deployment contract is present. Freshness is a single registry HEAD on the content-addressed ref — unchanged files never rebuild, never even upload. A build tars the staged build context (the exact file set the tag hashes), uploads it to the deployment's GCS bucket, and submits a Cloud Build job that runs as the deployment's least-privilege build SA. Auth is the pod's Workload Identity via the metadata server — pure urllib, no SDK dependency, no git remote, no stored credentials. Failures surface the build-log tail.
  • Gateway branch of cluster_for_run (engine/dask_cluster.py): create a cluster with the project image as the image cluster option, scale adaptively 1..--jobs, wait (bounded, default 600 s) for the first worker so an unpullable image fails loudly instead of hanging at zero workers, and shut the cluster down on exit — success or failure. Create/cull per run is PRD decision Add Dagster execution layer for reproducible analysis pipelines #1 and is also what makes image updates seamless: a Gateway cluster's image is fixed at creation. A startup assertion refuses clusters whose workers don't advertise the cpus/memory resource contract (the other silent-hang mode).
  • Self-provisioned worker environment — the deployment stays near-stock: lc reads the gateway's declared cluster options and passes everything workers need through the standard environment option — the DASK_DISTRIBUTED__WORKER__RESOURCES__* scheduling contract mirrored from the declared worker_cores/worker_memory, the driver's HOME/USER/LOGNAME (passwd-less uid-1000 images crash getpass.getuser() without them), and LIGHTCONE_WORKER_IMAGE (covering the deployment-default-image case too), preserving any deployment-declared environment defaults underneath. The hub's options handler reduces to the stock 2i2c daskhub shape (cores/memory/image/environment) plus the per-user home mount — zero lightcone-specific injection server-side (hub-deploy e10088c).
  • Executor rendezvous by name: gateway:// schedulers can't be dialled by a bare Client, so lc run hands the executor the cluster name (LIGHTCONE_GATEWAY_CLUSTER) and it rejoins through the authenticated Gateway API as a guest (shutdown_on_close=False).
  • Output through the future result: worker pods' stdout goes to pod logs, so _run_shell now returns (exit_code, sentinel_block) and the driver prints each finished rule's block — the one channel that works uniformly across LocalCluster threads, srun workers, and Gateway pods. This retires the cross-node stdout flock entirely (one less NFS/DVS concern), and a child snakemake that dies before the rule body (missing package in the worker image) forwards a bounded raw tail instead of vanishing (PRD architecture bullet / LCR-175 semantics). Legacy bare-int results from older workers are still accepted (version skew on image-based deployments). The executor also prefixes every spawned job with cd <workdir> — spawned snakemake commands carry no --directory, and Gateway worker pods start in the image's WORKDIR, not the project (local/SLURM workers only worked by inheriting the driver's cwd).
  • Site detection by env markers (engine/site_registry.py): a pod's hostname is noise; DASK_GATEWAY__ADDRESS is the signal. The jupyterhub site declares container_runtime: kubernetes and scratch_root: $HOMEno separate scratch space on the hub; home is the NFS volume shared with every worker pod, so driver and workers see one consistent snakemake state.
  • One snakemake invocation on every backend: --shared-fs-usage minus software-deployment is unconditional, so spawned jobs always run plain python from the worker's own environment (the worker image on the gateway; the driver's activated env locally/SLURM) instead of embedding the driver's sys.executable. No gateway-specific flags remain (--latency-wait dropped — the NFS latency was not reproducible in recent tests).
  • Environment-agnostic scaffoldlc init writes the same Containerfile everywhere; requirements.txt pins lightcone-cli, which carries the whole execution stack (snakemake, dask, distributed, dask-gateway — now a normal dependency, no [gateway] extra). No uid/USER is baked into the image: cluster pods run as the notebook uid via the deployment's securityContext (hub-deploy c8dc6cd), and getpass.getuser() works through the lc-provisioned USER/LOGNAME/HOME (LCR-176 Part A).
  • Provenance: the manifest keeps recording the declared spec, code_version hashes the registry ref (Containerfile edit → new ref → rerun), and a new additive worker_image field records LIGHTCONE_WORKER_IMAGE — provisioned into every pod by lc itself — as ground truth of what actually executed.

A spec declaring several distinct containers is rejected on this backend with guidance (the worker pod image is fixed per run — LCR-176 note); a project with no containers runs on the deployment's default image.

Out of scope (per PRD)

  • Cluster lifecycle beyond one run (clusters never survive between lc run calls).
  • Non-GCP build backends; general k8s support beyond the lightcone hub contract.

Relation to #162

Same PRD, different mechanism: #162 built through the BinderHub service (git-ref-based, requires committed/pushed state and a GitHub connect flow). This PR targets the newer Cloud-Build-based deployment (hub-deploy dask-cloudbuild), where builds are git-free tarball submissions — which removes the GitHub coupling and most of the surface area (~1.1k src lines vs ~4k).

Testing

  • just lint clean (ruff + strict mypy), 388 tests pass (+55 new: Cloud Build control flow against mocked HTTP seams incl. failure-tail and force-rebuild, gateway create/cull lifecycle incl. failure-path culling and zero-worker timeout, self-provisioned worker environment, an image-override regression test against the real dask-gateway client (lc's explicit image must beat the ambient DASK_GATEWAY__CLUSTER__OPTIONS__IMAGE default), executor rendezvous-by-name / workdir-prefix / legacy-result unpacking, sentinel-block forwarding, env-marker site detection, hub scratch resolution, runtime-aware status resolution, kubernetes-runtime Snakefile generation, environment-agnostic lc init scaffold).
  • Live end-to-end validated on the deployed lightcone hub (project union2.1_lcdm_fit, user pod → Cloud Build cached-image check → run-scoped Gateway cluster with the project image → all four rules executed in worker pods with per-rule output on the driver terminal → lc verify green, manifests recording worker_image ground truth → cluster culled on exit). The live test surfaced and fixed three integration bugs: gateway pods must use the modern dask scheduler/dask worker subcommands (deployment-side, hub-deploy bfec4ab), the executor must cd into the workdir for spawned jobs (c7b18e3), and the status walker must resolve image identities runtime-aware (50508b9). A second live validation (forced full re-run) was done after the near-stock streamlining (3533b25 + hub-deploy e10088c): all rules on workers, lc status/lc verify green, manifests carrying the lc-provisioned worker_image, cluster culled — and the run automatically picked up a changed build context (new content hash → Cloud Build → fresh cluster on the new image).

Refs: LCR-174 (PRD), LCR-176 (Part A + build path).

🤖 Generated with Claude Code

https://claude.ai/code/session_015wg3CVQ5oYWJBCfq58Z8d2

…lusters (LCR-174)

On a lightcone JupyterHub deployment (hub-deploy, dask-cloudbuild
branch) `lc build` and `lc run` now work with zero configuration:

- New `kubernetes` container runtime: the Dask worker pod *is* the
  container, so recipes run unwrapped and Containerfile specs resolve
  to registry refs (`$LIGHTCONE_REGISTRY/lc-<project>:<hash>`) — the
  same content-addressed identity as local tags, spelled for a
  registry.
- engine/cloudbuild.py: build backend selected by the deployment's env
  contract (LIGHTCONE_REGISTRY + LIGHTCONE_BUILD_BUCKET). Freshness is
  one registry HEAD; a build tars the staged (hashed) context to the
  build bucket and runs through GCP Cloud Build as the deployment's
  build SA. Auth = Workload Identity via the metadata server; pure
  urllib, no SDK, no git remote needed.
- cluster_for_run grows a Gateway branch: create a run-scoped cluster
  with the project image, adapt 1..jobs, wait (bounded) for the first
  worker, and cull it on exit — create/cull per run is what keeps the
  image fresh, since a Gateway cluster's image is fixed at creation.
  The executor rejoins the cluster by name through the Gateway API
  (gateway:// can't be dialled by a bare Client).
- Rule output now returns through the Dask future result and is
  printed by the driver — the only channel that reaches the terminal
  from Gateway worker pods — which also retires the cross-node stdout
  flock. Bootstrap failures (no sentinel output) forward a bounded raw
  tail.
- Site detection by env markers (DASK_GATEWAY__ADDRESS): runtime
  kubernetes, scratch = $HOME (the NFS volume shared with workers; no
  separate scratch space on the hub).
- Gateway snakemake invocation: --latency-wait 120 (NFS sync,
  LCR-177) and --shared-fs-usage without software-deployment so
  workers spawn their own `python`, not the driver's sys.executable.
- `lc init` on the hub scaffolds a worker-capable Containerfile
  (dask/dask-gateway/snakemake/lightcone-cli pinned to the ambient hub
  versions, uid-1000 user) — LCR-176 Part A.
- Manifests additionally record the pod-reported LIGHTCONE_WORKER_IMAGE
  as execution ground truth.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015wg3CVQ5oYWJBCfq58Z8d2
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 22, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
lightcone-cli a45e432 Commit Preview URL

Branch Preview URL
Jul 22 2026, 09:42 PM

The deployment injects DASK_GATEWAY__CLUSTER__OPTIONS__IMAGE (the
notebook image) as the client's ambient default; lc run's explicit
image kwarg must beat it or every cluster would run the notebook image
instead of the lc-build one. dask-gateway's _submit applies config
defaults first, then kwargs — a regression test now locks that in
(dask-gateway added to the dev group).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015wg3CVQ5oYWJBCfq58Z8d2
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

✅ Eval Results

Metric Value
Score 1.00
Build complete
Cost $1.68
Turns 62
Duration 378s
lightcone-cli 0.3.8.dev10+g194df9301 (194df930)
Results Download

Graders

✅ spec_valid (1.00)
✅ all_materialized (1.00)

Full output
tow-0.8.21 pytest-9.1.1 pytest-logging-2015.11.4 pyyaml-6.0.3 rapidfuzz-3.14.5 rdflib-7.6.0 referencing-0.37.0 requests-2.34.2 rich-15.0.0 rpds-py-2026.6.3 smart-open-7.7.1 smmap-5.0.3 snakemake-9.23.1 snakemake-interface-common-1.23.0 snakemake-interface-executor-plugins-9.4.0 snakemake-interface-logger-plugins-2.1.0 snakemake-interface-report-plugins-1.3.0 snakemake-interface-scheduler-plugins-2.0.2 snakemake-interface-storage-plugins-4.4.1 sortedcontainers-2.4.0 sqlmodel-0.0.37 tabulate-0.10.0 tblib-3.2.2 tenacity-9.1.4 throttler-1.2.3 toolz-1.1.0 tornado-6.5.7 tqdm-4.69.0 traitlets-5.15.1 typing-extensions-4.16.0 typing-inspection-0.4.2 urllib3-2.7.0 wrapt-2.2.2 yte-1.9.4 zict-3.0.0
21:41:42 lightcone.eval.sandbox [sandbox build] WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager, possibly rendering your system unusable. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv. Use the --root-user-action option if you know what you are doing and want to suppress this warning.
21:41:42 lightcone.eval.sandbox [sandbox build] #13 [10/11] WORKDIR /home/evaluser
21:41:43 lightcone.eval.sandbox [sandbox build] #14 [11/11] RUN mkdir -p ~/.claude && echo '{"hasCompletedOnboarding": true}' > ~/.claude.json
21:41:43 lightcone.eval.sandbox [sandbox build] #15 exporting to image
21:42:50 lightcone.eval.sandbox Created sandbox 064dba28-e97a-413d-996f-1a0d5cfef2ec for trial build-snae-0
21:42:51 httpx HTTP Request: POST https://proxy.app.daytona.io/toolbox/064dba28-e97a-413d-996f-1a0d5cfef2ec/files/bulk-upload "HTTP/1.1 200 OK"
21:42:52 lightcone.eval.sandbox Installed wheels: ['lightcone_cli-0.3.8.dev10+g194df9301-py3-none-any.whl']
21:42:54 httpx HTTP Request: POST https://proxy.app.daytona.io/toolbox/064dba28-e97a-413d-996f-1a0d5cfef2ec/files/bulk-upload "HTTP/1.1 200 OK"
21:42:54 httpx HTTP Request: POST https://proxy.app.daytona.io/toolbox/064dba28-e97a-413d-996f-1a0d5cfef2ec/files/bulk-upload "HTTP/1.1 200 OK"
21:42:54 httpx HTTP Request: POST https://proxy.app.daytona.io/toolbox/064dba28-e97a-413d-996f-1a0d5cfef2ec/files/bulk-upload "HTTP/1.1 200 OK"
21:42:55 httpx HTTP Request: POST https://proxy.app.daytona.io/toolbox/064dba28-e97a-413d-996f-1a0d5cfef2ec/files/bulk-upload "HTTP/1.1 200 OK"
21:49:16 lightcone.eval.sandbox Deleted sandbox for trial build-snae-0
  snae trial 0: score=1.00 complete

lightcone-cli: 0.3.8.dev10+g194df9301 (HEAD 194df930)
ASTRA: 0.2.10

  Eval Results: Scores  
┏━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Task ┃     Score     ┃
┡━━━━━━╇━━━━━━━━━━━━━━━┩
│ snae │ 1.00 +/- 0.00 │
│      │ pass@k: 100%  │
└──────┴───────────────┘

   Eval Results: Cost &   
         Duration         
┏━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Task ┃ Cost / Duration ┃
┡━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ snae │      $1.68      │
│      │      378s       │
└──────┴─────────────────┘

Total: 1 trials, $1.68, 378s

Results saved to: eval-results/build-194df930/results.json

EiffL and others added 4 commits July 22, 2026 09:34
Spawned snakemake job commands carry no --directory; remote executors
are expected to cd themselves (the official kubernetes executor does
the same). Local/SLURM workers inherit the driver's cwd by accident,
but Gateway worker pods start in the image's WORKDIR (e.g. /app) —
the child snakemake resolved relative paths there and died on a
read-only .snakemake. Found in the live e2e test on the lightcone hub.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015wg3CVQ5oYWJBCfq58Z8d2
On a kubernetes deployment `lc run` hashes the registry ref into each
manifest's code_version, but the status walker resolved Containerfile
specs to local-store tags — so every freshly materialized output read
as stale. New container.runtime_registry() is the single source of
truth for which spelling of the image identity a runtime uses; the
Snakefile generator and status walker both go through it. Found in the
live e2e test on the lightcone hub.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015wg3CVQ5oYWJBCfq58Z8d2
…ronment` option

The deployment's cluster-options handler no longer needs any
lightcone-specific injection: lc run reads the gateway's declared
options and passes, through the stock `environment` option, the
DASK_DISTRIBUTED__WORKER__RESOURCES__* scheduling contract (mirrored
from the declared worker_cores/worker_memory), the driver's
HOME/USER/LOGNAME (passwd-less uid-1000 images crash getpass.getuser()
without them), and LIGHTCONE_WORKER_IMAGE (manifest ground truth, now
covering the deployment-default-image case too). Ambient environment
defaults declared by the deployment are preserved underneath.

This makes the hub's dask-gateway config near-stock 2i2c daskhub —
the matching hub-deploy simplification removes the whole env block
from its options handler.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015wg3CVQ5oYWJBCfq58Z8d2
@EiffL

EiffL commented Jul 22, 2026

Copy link
Copy Markdown
Member Author

Streamlining update (3533b25), live-validated: the gateway branch now self-provisions the entire worker environment through the deployment's standard environment cluster option — the DASK_DISTRIBUTED__WORKER__RESOURCES__* scheduling contract (mirrored from the gateway's declared worker_cores/worker_memory), the driver's HOME/USER/LOGNAME, and LIGHTCONE_WORKER_IMAGE (now covering the deployment-default-image case too). The matching hub-deploy change (e10088c) reduces the options handler to the stock 2i2c daskhub shape (cores/memory/image/environment) plus the per-user home mount — zero lightcone-specific injection left server-side.

Validated on the deployed hub after rolling the near-stock config: forced full re-run of union2.1_lcdm_fit — all rules executed in worker pods, lc status/lc verify green, manifests carry the lc-provisioned worker_image, cluster culled on exit. As a bonus the run picked up a changed build context automatically (new content hash → Cloud Build → fresh cluster on the new image).

🤖 Generated with Claude Code

https://claude.ai/code/session_015wg3CVQ5oYWJBCfq58Z8d2

Comment thread src/lightcone/cli/commands.py Outdated
Comment thread src/lightcone/cli/commands.py Outdated
Comment thread src/lightcone/cli/commands.py Outdated
Comment thread src/lightcone/cli/commands.py Outdated
Comment thread src/lightcone/cli/commands.py Outdated
Comment on lines +819 to +824
* ``--shared-fs-usage`` without ``software-deployment`` — with it
(the default) the child snakemake command embeds the *driver's*
``sys.executable``, a path that doesn't exist inside the worker
image; without it, workers invoke plain ``python`` from their own
PATH. Everything else stays shared: the NFS home carries the
project, sources, and snakemake persistence.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Shouldn't this 'shared-fs-usage' without software-deployment be the default? does it break anything in the other execution backends if we have it everywhere?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes — it's now unconditional (93a2117). I checked snakemake's source: dropping software-deployment from --shared-fs-usage only changes the spawned job command from embedding the driver's sys.executable to invoking plain python (plus skipping --scheduler-solver-path/--conda-base-path, which we don't use). That's equally correct on local and SLURM, where workers inherit the driver's activated environment — the one scenario that changes is running lc outside an activated env (e.g. a bare pipx shim with no venv on PATH), which the project-venv convention already rules out. Validated with a fresh lc initlc runlc verify locally.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Flagging an interaction with the plugin de-bundling PR (#161) before we resolve this thread — the two PRs make opposite assumptions about the project venv, and this change tightens the coupling.

Making --shared-fs-usage exclude software-deployment makes the activated environment load-bearing for rule execution on the local and SLURM backends, in two places:

  • Driver: the top-level command is a bare snakemake console-script, so snakemake must be on PATH.
  • Spawned jobs: the dask executor now spawns plain python -m snakemake …, so python on the worker's PATH must be able to import snakemake + the dask executor plugin.

Both resolve today only because lc init installs lightcone-cli (hence the whole snakemake/dask stack) into the project .venv, which the session-start hook activates. That's the "project-venv convention rules it out" guarantee cited above — and this change now leans on it for spawned jobs too, not just the driver. The SLURM branch already hard-requires it (dask_cluster.py aborts if dask isn't on PATH inside the allocation's activated env).

The tension: #161 (de-bundling) moves to an empty project venv with a global lc. If that lands, the local/SLURM path here breaks — the driver's snakemake isn't on PATH (a global uv tool install exposes only lc/astra), and spawned python -m snakemake hits ModuleNotFoundError. The hub/Gateway path is unaffected: worker pods get the stack from the image via requirements.txt (added by this PR), never from the venv.

So whichever of #163/#161 merges second has to reconcile this. If we do drop the venv install, the options are roughly:

  1. Invoke snakemake through lc's own interpreter ([sys.executable, "-m", "snakemake", …]) and re-add software-deployment so spawned jobs use the same interpreter — clean, but partially reverts this thread and reintroduces a per-backend branch for the gateway (where the driver's interpreter path doesn't exist in the worker image).
  2. Keep a stack-bearing env on PATH some other way (a global env exposing snakemake/dask), leaving the empty project venv fine.

Not blocking this PR — just flagging so we resolve this thread with eyes open rather than silently.


Generated by Claude Code

Comment thread src/lightcone/cli/commands.py Outdated
Comment thread src/lightcone/engine/dask_cluster.py Outdated
Comment thread src/lightcone/engine/site_registry.py
Comment thread pyproject.toml Outdated
EiffL and others added 2 commits July 22, 2026 21:50
…affold, uniform invocation

- dask-gateway moves from the [gateway] extra (and dev group) into the
  main dependencies; the ImportError fallback in the gateway branch is
  gone with it.
- `lc init` scaffolds one Containerfile everywhere: no on-hub branching,
  no baked-in uid/USER (pod identity is deployment config — hub-deploy
  sets runAsUser on cluster pods), no per-dist worker-stack pins.
  requirements.txt instead pins lightcone-cli itself, which now carries
  the whole execution stack (snakemake, dask, distributed, dask-gateway)
  as normal dependencies.
- The snakemake invocation is identical on every backend: --latency-wait
  is dropped (no longer observed on the hub), and --shared-fs-usage
  minus software-deployment is now unconditional — spawned jobs run
  plain `python` from the worker's own environment on all backends
  (worker image on the gateway; the driver's activated env locally and
  on SLURM). The `gateway` parameter of _build_snakemake_cmd is gone.
- site_registry: keep scratch_root=$HOME on the hub but document why it
  can't be omitted (the tempdir fallback is pod-local, so worker-side
  .snakemake metadata would be lost and every run would look stale).

Validated end-to-end locally: lc init → lc run (rules spawn bare
`python -m snakemake` through the dask executor) → lc status / lc
verify green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live run on the redeployed hub failed with `getpwuid(): uid not found:
1000`: notebook pods don't necessarily export USER/LOGNAME (their own
passwd entry covers getpass there), so the forward-if-set approach
provisioned nothing — and the now environment-agnostic worker image has
no passwd entry for the pod uid, so the child snakemake crashed at
getpass.getuser(). Always set USER/LOGNAME in the provisioned worker
environment, deriving the name on the driver via getpass (env first,
passwd fallback; "jovyan" as the last resort).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@EiffL

EiffL commented Jul 22, 2026

Copy link
Copy Markdown
Member Author

Review round addressed in 93a2117 + a45e432 (with hub-deploy c8dc6cd, deployed as release revision 7): dask-gateway is a normal dependency, the scaffold is environment-agnostic (pod uid now comes from the deployment's securityContext), and the snakemake invocation is identical on every backend (no --latency-wait, --shared-fs-usage minus software-deployment unconditional).

The first live run on the redeployed hub caught one gap — notebook pods don't export USER/LOGNAME, and the passwd-less agnostic worker image can't fall back to getpwuid, so a45e432 derives the identity on the driver and always provisions it. Re-validated live after that fix: lc run from a user pod completes on Gateway worker pods with the agnostic image.

🤖 Generated with Claude Code

@aboucaud
aboucaud self-requested a review July 24, 2026 22:31
@github-actions

Copy link
Copy Markdown

Developer Certificate of Origin

This PR has been approved. Before it can be merged, all contributors must sign the Developer Certificate of Origin.

Status

none yet

How to sign

Post the following comment exactly as written:

I have read the Developer Certificate of Origin and I hereby sign the DCO for this PR

@aboucaud
aboucaud merged commit 8c1e818 into main Jul 24, 2026
13 of 14 checks passed
@aboucaud
aboucaud deleted the jupyterhub-gateway-cloudbuild branch July 24, 2026 22:31
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.

2 participants