Summary
compose.RenderEntrypointWrapper (added in #103) is a faithful port of the reference devcontainers/cli generateFeaturesComposeOverrideContent, with one deliberate gap: it does not implement overrideCommand gating of the original entrypoint/command.
Reference behavior
In src/spec-node/dockerCompose.ts the CLI computes:
const userEntrypoint = overrideCommand ? [] : composeEntrypoint || (imageEntrypoint…);
const userCommand = overrideCommand ? [] : composeCommand || (imageCmd…);
// …emits `command: <userCommand>` only when userCommand !== composeCommand
When overrideCommand is true, both the original entrypoint and command are zeroed — the generated wrapper runs the feature entrypoints, then exec "$@" hits empty args and falls through to the while sleep 1 & wait $!; do :; done keep-alive.
Our behavior
RenderEntrypointWrapper always preserves the original entrypoint (service's entrypoint:, else the image ENTRYPOINT) and never zeroes the command or emits a command: override. We do not read cfg.OverrideCommand on the compose path at all (serviceToRunSpec also ignores it).
Why this is low priority
Per the dev container spec, overrideCommand defaults to false for Docker Compose (true only for image/Dockerfile). With it false, the reference takes the composeEntrypoint || … / composeCommand || … branches — i.e. preserves exactly what we preserve. So for the default compose case (and the DAP devcontainer this was built for), our output is behaviorally equivalent to upstream.
The only divergence is an explicit "overrideCommand": true on a compose devcontainer: upstream would drop the service command and rely on the wrapper's keep-alive; we keep the command.
Options
- Implement
overrideCommand gating in the compose path for full parity (zero entrypoint/command + emit keep-alive command when overrideCommand is true), or
- Leave as-is and document the limitation.
Related
Summary
compose.RenderEntrypointWrapper(added in #103) is a faithful port of the referencedevcontainers/cligenerateFeaturesComposeOverrideContent, with one deliberate gap: it does not implementoverrideCommandgating of the original entrypoint/command.Reference behavior
In
src/spec-node/dockerCompose.tsthe CLI computes:When
overrideCommandis true, both the original entrypoint and command are zeroed — the generated wrapper runs the feature entrypoints, thenexec "$@"hits empty args and falls through to thewhile sleep 1 & wait $!; do :; donekeep-alive.Our behavior
RenderEntrypointWrapperalways preserves the original entrypoint (service'sentrypoint:, else the imageENTRYPOINT) and never zeroes the command or emits acommand:override. We do not readcfg.OverrideCommandon the compose path at all (serviceToRunSpecalso ignores it).Why this is low priority
Per the dev container spec,
overrideCommanddefaults to false for Docker Compose (true only for image/Dockerfile). With it false, the reference takes thecomposeEntrypoint || …/composeCommand || …branches — i.e. preserves exactly what we preserve. So for the default compose case (and the DAP devcontainer this was built for), our output is behaviorally equivalent to upstream.The only divergence is an explicit
"overrideCommand": trueon a compose devcontainer: upstream would drop the service command and rely on the wrapper's keep-alive; we keep the command.Options
overrideCommandgating in the compose path for full parity (zero entrypoint/command + emit keep-alive command whenoverrideCommandis true), orRelated
newRunSpecdoesn't consumecfg.Entrypoints).