Skip to content

fix(features): keep host variables raw in the metadata label - #69

Merged
aledbf merged 1 commit into
mainfrom
align-metadata-raw-vars
Nov 13, 2026
Merged

aledbf merged 1 commit into
mainfrom
align-metadata-raw-vars

Conversation

@aledbf

@aledbf aledbf commented Nov 13, 2026

Copy link
Copy Markdown
Member

Aligns the devcontainer.metadata label with the official CLI.

Problem

  • A Feature's mounts were resolved on the build host before the label was written. The image carried that host's paths (${localEnv:HOME} became /home/<user>) and applied them on every other host.
  • The metadata substitution wrote its result back through slices and maps shared with the label's entries. In compose, that rewrote base-image mounts in the label too.
  • Compose up never resolved host variables in Feature lifecycle commands.
  • read-configuration --include-merged-configuration printed them raw, where TS resolves them.

Now (Go = TS getDevcontainerMetadata/pickFeatureProperties)

Where Before After
Image label after build "source":"/home/aledbf" "source":"${localEnv:HOME}"
Compose up: image and container labels resolved raw
Container bind mount resolved resolved (on the host running up)
Compose Feature postCreateCommand with ${localEnv} ran raw resolved
read-configuration mergedConfiguration raw resolved

The Dockerfile ENV line for a Feature's containerEnv stays resolved at build time. That is the documented #308 fix: BuildKit rejects ${localEnv:…} there.

Tests (all fail on main)

  • TestExtendImageWithFeaturesResolvesFeatureHostVars, now scoped to the ENV line, plus the raw label mount.
  • TestBuildAndExtendComposeKeepsFeatureHostVariablesRawInTheLabel.
  • TestHostMetadataSubstitutionLeavesTheRawEntriesRaw: the write-through.
  • TestSubstituteFeatureHostVars and TestE2E_FeatureUserHomeAndLocalEnv adapted to the new contract.
  • 3 runtime parity cases against TS: build.feature-host-variables-raw-label-success, up.compose-feature-host-variables-raw-label-success, read-configuration.feature-host-variables-merged-success.

Validation (local)

  • task lint, task spec:compliance, task test:e2e (13 pass): pass.
  • task test:race with TMPDIR outside /tmp: pass.
  • task parity:contract: only the 2 known /tmp/.git cases fail.
  • Runtime parity: the new build and compose cases match. The new read-configuration case differs locally only in the workspace block, because of the /tmp/.git on this machine (same as main); its mergedConfiguration matches TS. The other local failures are the same /tmp/.git group, or Docker timeouts under parallel load that match when rerun alone.

🤖 Generated with Claude Code

A Feature's mounts were resolved on the build host before the image's
devcontainer.metadata label was written, so the image carried that
host's paths (${localEnv:HOME} became /home/<user>) and resolved them
the same way on every other host. The metadata substitution also wrote
its result back through mounts and maps it shared with the label's
entries, so in compose it rewrote base-image mounts in the label too.

As the reference CLI does, the label now keeps each Feature's values as
the Feature declares them, and up resolves them on the host it runs
on, from a copy: for single-container and compose, where Feature
lifecycle commands with host variables used to run unresolved, and in
read-configuration's merged configuration, which printed them raw.
Only the Dockerfile ENV line for a Feature's containerEnv stays
resolved at build time (upstream #308).

Three runtime parity cases compare the label, the compose labels and
the merged configuration with the reference.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@aledbf
aledbf merged commit dad66a7 into main Nov 13, 2026
13 checks passed
@aledbf
aledbf deleted the align-metadata-raw-vars branch November 13, 2026 01:38
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