Skip to content

Widen the workspace-id budget to 47, one inside devpod's fatal 48 - #283

Merged
blooop merged 2 commits into
mainfrom
longer_container_name
Aug 20, 2026
Merged

blooop merged 2 commits into
mainfrom
longer_container_name

Conversation

@blooop

@blooop blooop commented Aug 20, 2026 •

Copy link
Copy Markdown
Owner

What

TARGET_LENGTH goes from 38 to 47 — one character inside devpod's own ceiling of
48 — in both devlaunch/workspace_id.py and
rust/devlaunch-core/src/domain/workspace_id.rs.

38 was devlaunch's own budget, not a limit anything enforced. Two limits under it
are real:

  • 48, hard and fatal. devpod up --id <49 chars> is rejected outright —
    fatal prepare workspace client: workspace name cannot be longer than 48 characters, verified against the pinned v0.26.1. Not a truncation: a launch
    that does not happen.
  • 64 bytes, the hostname. The setup pass names the container after the
    workspace id, and downstream tooling stacks its own prefixes and suffixes onto
    that (kinisi-robotics/kinisi_ros#9766 already sat at 62/64 with a 38-char id).

38 was ten characters held back for the second. This spends nine of them on
legibility and keeps one against devpod's wall, leaving ~17 for whatever bolts
itself on — so a caller that wants more is now the one that has to shorten.

kinisi-ros-ags-devcontainer-t-lenevere     ->  kinisi-ros-ags-devcontainer-tooling-su-lenevere
devlaunch-dependabot-codecov-sifivasa      ->  devlaunch-dependabot-codecov-action-6-sifivasa
devlaunch-fix-gh-auth-in-devc-pedoveho     ->  devlaunch-fix-gh-auth-in-devcontainer-pedoveho

Uniqueness does not move with this. The eight-character syllable suffix is hashed
over the full (owner, repo, ref) triple before any truncation, so the readable
part is a legibility choice with no effect on the collision rate — which is why
every golden suffix here is untouched while the values are not.

What existing users see

That second point is the one thing worth a decision beyond this PR. See the
comment below.

Tests

Regenerated rather than hand-edited: rust/golden_vectors.py emits both golden
tables (workspace_id.rs, spec.rs), which is what that script exists for.

only_as_many_middle_segments_drop_as_needed needed a bigger fixture in both
languages: its 8-character segments now fit whole, so the test became "nothing
drops" and pinned nothing. It is now sized to the budget — over by less than one
segment, so exactly one middle has to go — and the old fixture is kept as its
opposite, a_ref_that_fits_whole_keeps_every_segment.

Run locally (rust via pixi exec -s rust, which is not in the project env):

  • cargo test -p devlaunch-core: 1138 passed, 3 failed — all three
    real_git_*_lfs, on git-lfs: not found in this container, not this change.
  • cargo test -p dl --lib --bins --test grammar --test launch --test migration --test lifecycle --test read_side: green (73 / 5 / 42 / 37 / 1).
  • cargo clippy --all-targets -- -D warnings, cargo fmt --check: clean.
  • pytest test: 1887 passed, 2 failed — both test_devcontainer_manifest.py
    asserting pixi task list output, pre-existing in this container and unrelated.

🤖 Generated with Claude Code

Summary by Sourcery

Widen workspace ID generation to 47 characters so longer repository and branch names remain readable without exceeding devpod’s limit.

New Features:

  • Increase the maximum generated workspace ID length from 38 to 47 characters while preserving collision-resistant suffixes and allowing more branch-name detail.

Enhancements:

  • Document the updated workspace-ID length, downstream hostname constraints, and behavior for existing containers and clone directories.
  • Regenerate workspace-ID golden vectors and expand truncation tests to cover the widened budget and middle-segment retention.

Documentation:

  • Update the changelog and README examples and constraints for 47-character workspace IDs.

Tests:

  • Refresh Rust and Python workspace-ID expectations and add coverage for refs that fit within or narrowly exceed the new budget.

@sourcery-ai sourcery-ai 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.

Sorry @blooop, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@sourcery-ai

sourcery-ai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Reviewer's Guide

Increase workspace-id maximum length from 38 to 47 characters (one below devpod’s hard 48-char limit), adjust truncation behavior and tests to keep more of long repo/ref segments while preserving uniqueness and hostname constraints, and document the new behavior in changelog and README.

File-Level Changes

Change Details Files
Increase workspace id target length from 38 to 47 characters while keeping within devpod and hostname constraints.
  • Raise TARGET_LENGTH/TARGET_LENGTH constant from 38 to 47 in both Python and Rust workspace id implementations.
  • Expand inline documentation explaining devpod’s 48-character fatal ceiling and the 64-byte hostname limit and rationale for reserving ~17 characters for downstream prefixes.
  • Update comments describing how suffix syllables were added without changing the overall cap.
devlaunch/workspace_id.py
rust/devlaunch-core/src/domain/workspace_id.rs
Adjust slugging and truncation math so long repo and ref names retain more readable tail within the new budget.
  • Update ref-budget comment and calculation to reflect 17-character minimum for refs instead of 8.
  • Ensure long repo names and long branch / ref paths are truncated to wider readable labels, taking advantage of the increased budget.
  • Keep uniqueness behavior unchanged by continuing to hash suffix over full (owner, repo, ref).
devlaunch/workspace_id.py
rust/devlaunch-core/src/domain/workspace_id.rs
Refresh golden vectors and tests to match new workspace id shapes and segment-dropping behavior.
  • Update Rust golden spec cases and workspace-id golden vectors to expect longer repo labels and workspace ids under the 47-char budget.
  • Add and adjust test cases for dependabot refs, long branch names, and multi-segment refs so only as many middle segments are dropped as needed and refs that fit wholly keep all segments.
  • Regenerate golden tables via rust/golden_vectors.py, adding a new long-segment ref fixture.
  • Mirror truncation and segment-dropping tests in Python to match Rust behavior.
rust/devlaunch-core/src/domain/spec.rs
rust/devlaunch-core/src/domain/workspace_id.rs
rust/golden_vectors.py
test/test_workspace_id.py
Document new workspace id length and behavior for users.
  • Add changelog entry describing the widened workspace id budget, devpod’s 48-char limit, hostname constraints, and impact on existing containers and clone directories.
  • Update README workspace id examples to show 47-character maximum and new, less-truncated workspace id examples, including dependabot and long-branch cases.
  • Clarify hostname budget explanation so downstream tools know they must shorten their own prefixes if they exceed remaining headroom.
CHANGELOG.md
README.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@blooop

blooop commented Aug 20, 2026

Copy link
Copy Markdown
Owner Author

Self-review

Read the diff back against the four things this change can get wrong.

The cap itself is the only behavioural line. TARGET_LENGTH 38 → 47 in both
runtimes; every other line is a comment, a doc, a regenerated golden, or a
fixture. The derivation is untouched, so the two implementations still agree —
which is what the golden tables are for, and they were regenerated by
rust/golden_vectors.py rather than edited by hand.

Suffixes must not move, and none did. Every changed golden value keeps its
suffix; only the readable part grew. That is the property worth checking, because
the suffix is hashed over the full triple before truncation, so a moved suffix
would mean the identity had changed and every workspace on disk had been renamed
for real. TestSyllableSuffix's "DO NOT CHANGE" table is byte-identical.

One test stopped testing what it says, and one nearly did.
only_as_many_middle_segments_drop_as_needed used 8-character segments, which
now fit whole — so it asserted "nothing drops" and pinned nothing. Fixed by
sizing the fixture to the budget, with the old fixture kept as its opposite
(a_ref_that_fits_whole_keeps_every_segment). Reviewing that led to the second
commit: the_first_and_last_segments_survive_heavy_truncation was passing by a
single character (36 against a 35-character ref budget), so one more character of
cap would have silently demoted it to a one-middle test. Both middles are now 40.

The invariant tests are all cap-relative, deliberately. Every length
assertion in test_workspace_id.py, test_dl.py and the Rust module reads
TARGET_LENGTH rather than a literal, so they moved with the constant and would
have caught an overflow at any value. The = 8 in the ref-budget comment is now
= 17, which is the same arithmetic re-run, not a new claim.

Nothing else in the tree encodes the old number. dl.py's ref-less repo
label and spec.rs's repo_label both cut at TARGET_LENGTH, so a 47-character
repo label is still inside devpod's 48. Path specs (dl ./some/dir) remain the
one route that never passes through this module and is capped by nothing — an
existing gap this change neither widens nor closes.

Decision recorded

The clone-directory leaf changes for long names, so an affected workspace gets a
fresh clone under the new leaf on its next launch and the old directory is left
where it is. Ships that way on purpose: the container still opens (the recorded
devpod id wins over the derivation), and the stale directory becomes one no
record claims, which is exactly what dl --prune lists and offers to remove.
Bumping SCHEMA_VERSION to re-run the rename migration was considered and not
done — it would repurpose a shape version as a re-derive trigger and print a
migration report on every user's next launch to tidy a directory --prune
already reports.

🤖 Generated with Claude Code

blooop and others added 2 commits August 20, 2026 18:37
38 was never a limit anything enforced. Two limits are real underneath it: devpod
rejects a 49-character `--id` outright (`workspace name cannot be longer than 48
characters`, verified against the pinned v0.26.1), and the container hostname is
capped at 64 bytes, which devlaunch shares with downstream tooling that stacks
its own prefixes onto the name (kinisi-robotics/kinisi_ros#9766 already sat at
62/64). 38 was devlaunch holding ten characters back for the second one.

This spends nine of them on legibility and keeps one against devpod's wall.
`kinisi_ros@ags-devcontainer-tooling-support` now reads
`kinisi-ros-ags-devcontainer-tooling-su-lenevere` instead of losing the branch's
tail, a dependabot ref keeps its whole action name, and ~17 characters are still
there for whatever bolts itself onto the container name -- a caller that wants
more is now the one that has to shorten.

Nothing about uniqueness moves with this: the eight-character suffix is hashed
over the full (owner, repo, ref) triple before any truncation, so the readable
part is a legibility choice with no effect on the collision rate. That is why the
golden tables' suffixes are untouched here while their values are not.

The golden tables in both Rust modules are regenerated by `rust/golden_vectors.py`
rather than hand-edited, which is the discipline that file exists for.

`only_as_many_middle_segments_drop_as_needed` needed a bigger fixture in both
languages: its 8-character segments now fit whole under the wider budget, so the
test it was became "nothing drops", pinning nothing. It is now sized to the
budget -- over by less than one segment, so exactly one middle has to go -- and
the old fixture is kept as its opposite, `a_ref_that_fits_whole_keeps_every_segment`.

Co-Authored-By: Claude <noreply@anthropic.com>
`the_first_and_last_segments_survive_heavy_truncation` claims the ends of a
path-shaped ref survive however many middles have to go. With 30-character
middles it was making that claim with a single character to spare under the wider
budget: `aa-<30 n>-zz` is 36 against a 35-character ref budget, so one more
character of cap would have quietly turned it into a test of dropping *one*
middle, still green and no longer testing what it says.

40 each puts it far past the budget rather than just over it, in both languages,
and the golden corpus moves with it because `rust/golden_vectors.py` is where the
Rust table's inputs live.

Co-Authored-By: Claude <noreply@anthropic.com>
@blooop
blooop force-pushed the longer_container_name branch from e5167cb to c8a0482 Compare August 20, 2026 18:37
@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.26%. Comparing base (1b01ac2) to head (c8a0482).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #283   +/-   ##
=======================================
  Coverage   93.26%   93.26%           
=======================================
  Files          25       25           
  Lines        3774     3774           
=======================================
  Hits         3520     3520           
  Misses        254      254           
Files with missing lines Coverage Δ
devlaunch/workspace_id.py 100.00% <100.00%> (ø)

Impacted file tree graph

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@blooop
blooop merged commit 3ac7038 into main Aug 20, 2026
15 checks passed
@blooop
blooop deleted the longer_container_name branch August 20, 2026 18:42
@blooop blooop mentioned this pull request Aug 20, 2026
blooop added a commit that referenced this pull request Aug 20, 2026
Main moved 43 commits and four releases (0.2.0 through 0.3.0) while this branch
was being written, and two of those commits touch the same ground. Reconciled
rather than re-applied, since main thought about one of them more carefully than
this branch had.

**The Rust toolchain in the pixi environment (#284) was already done, better.**
This branch had added `rust` as a plain dependency; main added it as a *feature*
carried only by `default`, so the py31x solves and their CI caches do not each
drag in a 1.5GB toolchain, with the 521MB→2044MB disk trade written down. Main's
design is kept and the plain dependency dropped; the collapse to one environment
in this branch then removes the reason the feature had to be scoped at all, and
its comment says so.

One correction on top of it: the pin was `rust = "1.97.*"`, while AGENTS.md told a
reader the environment is "pinned to the 1.97.1 that rust-toolchain.toml names".
Those disagree the day 1.97.2 is published — silently, and only inside the
container, which is the worst place for a toolchain difference to surface.
Tightened to `1.97.1.*`, which is what the lockfile already held, so it costs no
re-solve and makes main's own sentence true. `test_agents_doc.py` now reads the
feature and holds the two to each other exactly.

**Nothing was lost to the deletion.** The two Python modules main edited in that
window were checked against their Rust counterparts before being removed:
`workspace_id.py`'s `TARGET_LENGTH = 47` (#283) is `TARGET_LENGTH: usize = 47` in
`domain/workspace_id.rs`, and `tools.py`'s `PIXI_HOME_TARGET = "$HOME/.devlaunch/pixi"`
(#277) is `PIXI_HOME_EXPORT`/`PIXI_BIN_LINE`/`CLAUDE_SHIM_BIN_LINE` in
`flows/provision.rs`, with byte-goldens pinning the rendered scripts. Both sides
moved together, which is what the parity discipline was for.

The rest was mechanical: main's `pixi.lock` is lock-file version 7, which needed
a newer pixi than the container had, and the manifest's dependency changes here
required a fresh solve (932 lines shorter, four environments gone). README keeps
main's new paragraph about cargo needing no toolchain of your own — that one is
about the crates, not about the Python build this branch ends. `test/unit/test_devcontainer_manifest.py`
gained 333 lines on main and imported `tomli`, which this branch removes; it reads
`tomllib` now, like its two siblings.

Also, while re-reading the goldens that survive: the `PYTHON_*` constants in
`flows/provision.rs` are named for where they were transcribed from, and that
module is now gone. Kept under their names — what they pin did not change when
their author left, and the name is the one thing still recording that nobody read
them off the implementation they judge — with a note saying exactly that, and
saying that there is nothing left to re-derive them from.
blooop added a commit that referenced this pull request Aug 20, 2026
`the_repo_slug_floor_still_leaves_the_ref_room` asserted `room == 8`,
which was 38 - 20 - 8 - 2 when it was written. #283 widened
TARGET_LENGTH to 47 (one inside devpod's fatal 48), so the room the
repo-slug floor leaves is 17, and the test failed on rebase asserting 8.

The literal stays a literal. Deriving it from the same constants the
line above already uses would assert nothing at all, and the failure is
the ratchet working: a budget change should have to be acknowledged
here rather than absorbed silently. The comment now says so, and records
what the number was.

Nothing else in the test needed touching -- the layout assertions below
are written in terms of `room`, so they followed it. 65 tests in the
module pass; clippy and fmt are clean.
blooop added a commit that referenced this pull request Aug 20, 2026
#283 widened TARGET_LENGTH to 47, so the room fit_ref gets is 17, not the
8 it was at 38. The assertion moved with it; this sentence did not.
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.

1 participant