build: bump jupyterlab-launchpad to 1.1.1 in jupyterlab image - #241
build: bump jupyterlab-launchpad to 1.1.1 in jupyterlab image#241tylerpotts wants to merge 3 commits into
Conversation
Picks up the latest upstream release. Dependency set is unchanged from 1.1.0, so only the launchpad wheel entry moves in the lock file.
viniciusdc
left a comment
There was a problem hiding this comment.
Went over this one carefully. The change itself is correct and correctly scoped — two files matches every sibling bump, and the post-merge tag automation owns values.yaml, so there's nothing to add there. Verified the sha256 against PyPI (24a7af66... matches the published 1.1.1 wheel) and requires_dist is byte-identical to 1.1.0, so your "nothing else in the resolution changes" holds. Nothing here blocks merging.
Two inline comments on the lock and the pin. The rest doesn't land on a changed line, so it's here.
Test plan item 2 won't do what it says. test.yaml:61 only reads singleuser.image.tag for the prep cache and the kind side-load — the pod that actually spawns takes its image from the default profile's kubespawner_override.image at values.yaml:507, because _hub.py:56 posts with no profile options so the default: true profile wins. So bumping only the singleuser tag side-loads the PR image and then spawns sha-08ea5e4 over the network anyway. scripts/bump_image_tags.py 435c121 syncs all of them (bare sha — the script adds the sha- prefix itself, passing sha-435c121 gives you sha-sha-435c121). And use sha-435c121 rather than pr-241: pr-241 is mutable and the e2e cache key is the ref string, so a later push to the branch rebuilds the tag while every leg happily restores the stale tar.
Worth saying in the body that the green e2e legs didn't test 1.1.1. They ran against sha-08ea5e4, which carries 1.1.0, and would be equally green if this bump were broken. Not your fault — it's just how the pipeline resolves the image — but 31 green checks read as coverage to anyone skimming. The genuine signal is the jupyterlab amd64/arm64 legs: Dockerfile:94 runs pixi install --locked, so those do prove the lock resolves and installs.
Can you link #227? Upstream launchpad#87 is the whole content of 1.1.1 and opens with "Launchpad side fix for data-science-pack issue 227", and @kcpevey called this bump the step before the ds-pack release on that thread. The part that's invisible right now and is really the point of the PR: the sender half is already deployed — nebi#538 merged 2026-08-27, v0.15 published at 5ca877a, and values.yaml:256 already pins sha-5ca877a. So once this lands the round trip is complete and actually testable. Release notes are --generate-notes, so the body is the only place this gets recorded.
Careful with the Chart.yaml bump. release.yaml fires on a push touching Chart.yaml and packages values.yaml as it stands, but the tag carrying 1.1.1 only gets written post-merge by bump-image-tags.yaml. If the chart goes to 0.1.6 before that bot commit lands, the released chart still pins sha-08ea5e4 — 1.1.0 — and #227 isn't actually closed. Leaving it at 0.1.5 here is right, just worth confirming the chart bump waits for the chore: bump image tags commit.
On testing it — IMO add nothing to this repo, verify by hand. nebi:job-completed doesn't appear anywhere in this repo, both halves of the contract live upstream, and #87 already ships 193 lines of unit coverage for the listener. Reproducing it here needs a browser driver (tests/e2e is HTTP + kubectl exec only), a deployed Nebi + Keycloak, and a real env-build job — that's a new test stack, not a new test. What I'd do manually, on nebi sha-5ca877a + jupyterlab sha-435c121:
jupyter labextension listreports launchpad 1.1.1.- Open the Nebi tab and leave it open — the listener only fires while the iframe is mounted.
- Trigger an install for a kernel that isn't already in the kernelspec list.
- Time it from the Nebi pill flipping to complete. Should be seconds, no page reload; >60s means the handshake didn't fire.
- Negative control, worth the 30 seconds: same thing with the tab closed, or on
sha-08ea5e4, and confirm the ~61s wait is still there. Otherwise you haven't separated the fix from cache-timing luck.
Two follow-ups I'd open separately rather than bolt onto this bump. The e2e suite structurally can't run against the PR-built image today — worth fixing, though note a hand edit to values.yaml on main is transient since the bot rewrites it on the next build. And an extension-presence leg (kubectl exec, the pattern at test_shared_storage.py:75, assert launchpad shows enabled at the pinned version) would catch the one class nothing catches now: an extension pixi installs fine but that's broken or disabled at runtime. Costs a full matrix leg and doesn't touch the postMessage coupling, so only if you think that class is real. Also nebi-integration.md:207-214 has no troubleshooting row for #227's symptom — purely additive, nothing existing goes stale.
A couple of things I checked and deliberately don't think are worth raising: PyPI attestations are missing at 1.1.1 and 1.1.0 alike (upstream publishes through jupyter_releaser/twine, which doesn't mint PEP 740), so pre-existing and not a regression. The postMessage origin check is present and correct upstream and the refresh is flood-coalesced, so there's no exploit there. And the pin resolution is clean — jupyter_server 2.20.0 satisfies launchpad's >=2.0.1,<3.
- regenerate pixi.lock with pixi update jupyterlab-launchpad so entries keep pixi's sort order - document that launchpad >=1.1.1 depends on the nebi binary >= v0.15
Temporary: the post-merge bump-image-tags workflow rewrites these.
|
Addressed the review-body items in 793ba18 and 26b59e9:
Not done here: the two follow-ups (e2e can't run against a PR-built image without a hand edit; extension-presence leg / troubleshooting row for #227). Those are separate issues; happy to open them if you want them tracked. |
viniciusdc
left a comment
There was a problem hiding this comment.
The lock came back genuinely clean this time and the tag sync is byte-identical to script output — I re-ran scripts/bump_image_tags.py 7c0599c over the head and it's a no-op with an empty diff, so all 9 refs agree. One thing I'd settle before merging, and it isn't the code.
The fix only works while the Nebi tab is mounted
useHostJobNotifications gates on isEmbedded() && isLocalMode, and the poll only runs while the Nebi React tree is rendered. So: start an env build, switch away so the tab unmounts, and the completion message is never posted — you're back to the flat 61s KernelSpecManager cycle.
#227 asks about "delay between environment completing and showing up as available", unqualified, and merging auto-closes it. That's a partial fix landing as a complete one. One sentence in the body would cover it.
It also reframes the checklist: item 3's "Nebi tab closed" isn't only a negative control, it's a still-supported path this PR doesn't improve.
The manual verification is the test suite here, and it hasn't been run
Body says "Verified by hand instead, see below" — but the three items below are all - [ ] and nothing records a result. Your own comment says they're "a checklist to run", so I think the sentence is just stale tense. Worth fixing either way since nothing in CI touches the handshake.
Two things while you're in there: there are three items, not five (the "five" is in your comment). And item 1 doesn't need a human at all — jupyter labextension list is a one-line docker run against the built image, which would sit nicely as a post-build smoke step in build-image.yaml, since that workflow has no post-build verification of any kind today.
On item 3 — the confound to rule out isn't the image, it's phase in the 61s cycle. A kernel landing 55s in looks instant on 1.1.1 by luck. Reload the page right before installing so the poll clock resets and the no-fix floor really is ~61s, then repeat item 2 twice.
The nebi >= v0.15 floor is only recorded where no deployer looks
images/jupyterlab/pixi.toml:81-82 is the only record of the coupling in the whole repo. Meanwhile nebi-integration.md:77-79 tells deployers to override nebi.image.tag "to test a PR build or to roll forward" with no floor named, and values.yaml:250-256 says sha-5ca877a == the nebi v0.15 tag commit without saying what breaks below it. Roll nebi back and the 61s poll returns silently — no log line anywhere.
A numeric gate is impossible since that's a content sha, but we already have the pattern for exactly this ungateable case with jhub-app-proxy-version: floor at the value site (values.yaml:393-396), prose at the override site (nebi-integration.md:193-195), and a symptom row (nebi-integration.md:214). Three lines, same shape.
Worth folding in one more: isLocalMode() comes from nebi's /version, and nebi's modeStore falls back to team mode after 3 failed attempts (~900ms). NEBI_MODE: "local" is hardcoded at images/nebi/jupyter_server_config.py:37 so this only bites on a slow nebi start, but the handshake can no-op after a transient startup race with nothing in the logs.
A ~20-line unit assert would guard the hand edit
e2e's cache key and kind side-load come from jupyterhub.singleuser.image (test.yaml:61, :150), but the pod that actually spawns comes from the default profile — _hub.py:56-65 POSTs /server with no body, so kubespawner falls through to the default: true profile and its profile_options default choice overwrite the image. Today they agree, so this is latent rather than broken. But a bump that moves singleuser.image.tag and misses a profile ref would have e2e report the new ref while the pod pulls the old image — green on 1.1.0.
Nothing guards it: test_hub_ca_bundle.py:99-115 covers only the hub/init pair and there are no hardcoded shas in tests/. A yaml.safe_load assert following the existing _hub_values() pattern catches it and joins the existing unit job — no kind, no pull, no matrix leg. Add assert jh["hub"]["image"]["tag"] == jh["singleuser"]["image"]["tag"] too, otherwise a half-bump that moves the hub pair and leaves the seven jupyterlab refs still passes everything.
Not suggesting the extension-presence e2e leg — max-parallel is 10 and the matrix already collects exactly 10 node IDs, so an 11th test buys a whole second wave.
Smaller things
values.yaml:492-495undercounts what a hand-editor has to bump — it names only thechoices.defaultimage lines, omitting the outerkubespawner_override.imageanddisplay_name, while:502-506correctly says the script syncs all three. Pre-existing, not from this PR, but :492 is the comment you hit first and this PR is exactly a hand edit.images/Dockerfile:17installs pixi unpinned (PIXI_VERSIONappears nowhere). Pre-existing and not a security issue — I checked,pixi install --lockeddoes verify the lock's PyPI sha256 on both cold and warm cache paths — but a pixi minor bump can change lock handling with no repo change, so pinning it is worth an issue.- Follow-up, not this PR: the
sha-tag on apull_requestbuild comes from the merge commit, so a pin can never name the build of the commit containing it. Any push touchingimages/**after the pin lands means e2e goes green against stale image content, silently. You dodged it here by ordering the pin last.
Checked and not raising
I read launchpad#87 — v1.1.0...v1.1.1 is 2 commits, 8 files, +284/-15, all on-topic, no new deps, 172 lines of tests included. The lock's sha256 matches PyPI and my own independent pixi lock. quay has sha-7c0599c multi-arch with 4/4 children, and its base is e898d86, which is current main head, so the image content equals post-merge main.
Also: the hub does roll on upgrade (the pod-template image itself changes); both profiles' display_name matches their image so the selector shows what it runs; a fork PR pinning an unpushed tag fails loudly at docker pull, not silently; and there's no GPU-ref skew since values.yaml has no GPU ref at all.
Two things I'm explicitly withdrawing from my earlier review. The Chart.yaml release-window risk I raised is much smaller than I said — 0.1.5 is already released and every packaging step in release.yaml is gated on exists == 'false', so a Chart.yaml push that doesn't bump the version is a no-op. And Closes #227 is fine as-is: #236 closed #191 the identical way, one second after merge, with its fix still unreleased four days later. Body line 14 is the right disclosure, not a contradiction.
Summary
jupyterlab-launchpadfrom1.1.0to1.1.1inimages/jupyterlab/pixi.toml(upstream release: https://github.com/nebari-dev/jupyterlab-launchpad/releases/tag/v1.1.1). The whole content of 1.1.1 is Refresh Launchpad kernels after Nebi workspace jobs complete jupyterlab-launchpad#87: Launchpad listens fornebi:job-completedfrom the Nebi iframe and refreshes kernels immediately instead of waiting on the ~61s KernelSpecManager poll.pixi.lockwithpixi update jupyterlab-launchpad(Linux container).requires_distis identical to 1.1.0, so only the launchpad wheel entry changes.Closes #227. The sender half is already in place: nebari-dev/nebi#538 shipped in nebi v0.15 and #240 pinned
nebi.image.tag: sha-5ca877aon main. This PR completes the round trip.What CI does and does not prove
jupyterlabamd64/arm64 build legs runpixi install --locked, so they prove the lock resolves and installs.sha-08ea5e4(1.1.0) and did not exercise 1.1.1.values.yamlis now pointed at the PR buildsha-7c0599cviascripts/bump_image_tags.pyso subsequent e2e legs spawn the 1.1.1 image. The post-mergebump-image-tagsworkflow rewrites these tags anyway.postMessagehandshake itself (needs a browser, a deployed Nebi + Keycloak, and a real env-build job). Verified by hand instead, see below.Release note
Do not bump
Chart.yamluntil the post-mergechore: bump image tagscommit lands.release.yamlpackagesvalues.yamlas it stands, so a chart bump before that commit would ship a chart still pinning 1.1.0 and #227 would not actually be closed.Manual verification (nebi
sha-5ca877a+ jupyterlabsha-7c0599c)jupyter labextension listreports launchpad 1.1.1sha-08ea5e4) still shows the ~61s delay