Feature request: First-class array input for Feature options (array option type + multiple invocations) [blocked on spec#766]
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 35/100
- Issue type
- Feature
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
Research direction
Start with the spec#766 RFC, since this implementation is explicitly blocked on its acceptance. Then read src/spec-configuration/containerFeaturesConfiguration.ts and configuration.ts, followed by getFeatureLayers and getFeatureInstallWrapperScript; inspect devContainerFeature.schema.json for schema changes. Done means array options, coercion, serialization, repeated CLI flags, multiple invocations, validation, and the listed test-plan cases all work.
Written by the indexing model from the issue text.
Description
Feature request: First-class array input for Feature options (array option type + multiple invocations)
Complex feature request — depends on the spec change in
devcontainers/spec#766 (RFC).
This issue tracks the reference CLI implementation work once the spec RFC is accepted.
Related long-standing issues: spec#57
(array option type, open since 2022) and spec#44
(install a feature more than once).
Problem
The dev container spec restricts Feature option values to boolean and string. There is no
array type. As a result, every Feature that needs a list (packages, extensions, tools, versions)
is forced to accept a comma-separated string and split it inside install.sh.
This CLI encodes that limitation directly in its TypeScript types, which is the root cause that
blocks any progress:
// src/spec-configuration/containerFeaturesConfiguration.ts
export type FeatureOption = {
type: 'boolean';
default?: boolean;
description?: string;
} | {
type: 'string';
enum?: string[];
default?: string;
description?: string;
} | {
type: 'string';
proposals?: string[];
default?: string;
description?: string;
};
// src/spec-configuration/configuration.ts
export interface DevContainerFeature {
userFeatureId: string;
options: boolean | string | Record<string, boolean | string | undefined>;
}
// ...
features?: Record<string, string | boolean | Record<string, string | boolean>>;
FeatureOption has no 'array' variant. DevContainerFeature.options and the features map
only permit boolean | string for option values — arrays are not representable, so they can
never reach install.sh no matter what a user writes in devcontainer.json.
Real-world impact (shipped Features, today)
| Feature | Option | Today | Symptom |
|---|---|---|---|
ghcr.io/rocker-org/devcontainer-features/r-packages:1 |
packages |
"cli,rlang" |
Comma-joined; values containing commas are unrepresentable. |
ghcr.io/devcontainers/features/github-cli |
extensions | "github/gh-copilot" |
Comma-joined; extension refs/args with commas break. |
mwmahlberg/devcontainer-features npm-packages |
packages |
"typescript,eslint" |
Accepts comma or whitespace or newline — three delimiters, because the spec gives no canonical list form. |
The npm-packages case is the clearest failure signal: with no array type, every Feature author
invents a different delimiter. Consumers cannot reason about a list option without reading each
Feature's install.sh.
This was always meant to be temporary. From spec#57,
maintainer @Chuxel (2022):
Right now things in devcontainers/features are using a comma separated string as a near term
workaround. Converting this into an array is pretty easy…
That workaround has now been the de facto standard for 3+ years.
Requirements (hard — no alternatives)
This is a required capability, not a nice-to-have. Comma-separated strings are not an acceptable
long-term substitute (they are ambiguous, lossy, and force per-Feature delimiter conventions). The
CLI must implement:
arrayoption type —devcontainer-feature.jsonmay declare"type": "array"for an
option, withdefaultas an array andproposals/enumconstraining elements.- Array option values in
devcontainer.json—"packages": ["curl","git","jq"]must be
accepted, validated, and propagated. - Multiple invocations via array of option objects — a Feature value may be an array of option
objects ("dotnet": [{"version":"3.1"},{"version":"6.0"}]), invokinginstall.shonce per
element in order (resolves spec#44). - Option Resolution for arrays — array values are serialized to
devcontainer-features.envas
a JSON array string (PACKAGES='["curl","git","jq"]'), the only delimiter-free, unambiguous
encoding. - Backward-compatible string→array coercion — if a string is supplied for an
array-typed
option: parse as JSON if it looks like a JSON array, else split on commas. Existing
comma-separated Features keep working when they migrate totype: "array". - CLI flag support — accept JSON array values in
--override-features, and support repeated
flags (--feature-option <feature>.<option> <value>) that append to an array option.
Proposed implementation
1. Type changes
src/spec-configuration/containerFeaturesConfiguration.ts — extend FeatureOption:
export type FeatureOption = {
type: 'boolean';
default?: boolean;
description?: string;
} | {
type: 'string';
enum?: string[];
default?: string;
description?: string;
} | {
type: 'string';
proposals?: string[];
default?: string;
description?: string;
} | {
type: 'array'; // NEW
enum?: string[]; // constrains elements
proposals?: string[]; // suggests elements
default?: (string | boolean | number)[];// array default
description?: string;
};
src/spec-configuration/configuration.ts — widen option value and Feature value types:
export interface DevContainerFeature {
userFeatureId: string;
// allow arrays of primitives as option values, and array-of-option-objects as the Feature value
options: boolean | string | (string | boolean | number)[] | Record<string, boolean | string | (string | boolean | number)[] | undefined> | Record<string, boolean | string | (string | boolean | number)[]>[];
}
// ...
features?: Record<string, string | boolean | (string | boolean | number)[] | Record<string, boolean | string | (string | boolean | number)[]> | Record<string, boolean | string | (string | boolean | number)[]>[]>;
2. Option parsing & normalization
Where feature option values are read and normalized (the getFeatureValueDefaults /
option-resolution path in containerFeaturesConfiguration.ts):
- When
option.type === 'array':- Accept a JSON array value as-is.
- If the supplied value is a string, attempt
JSON.parse; if it yields an array, use it;
otherwise split on,and trim each element. Emit a warning recommending the array form. - Validate each element against
enum/proposalswhen present. - Default to
[]when omitted and nodefault.
3. Option Resolution (env serialization)
In the code that writes devcontainer-features.env (<OPTION_NAME>=<value>):
- For an
arrayoption, serialize the value withJSON.stringify(value)so the env var holds the
canonical JSON array string. Example output:PACKAGES='["curl","git","jq"]' - This keeps a single, unambiguous source of truth.
install.shparses withjq(already
ubiquitous in dev container images):for pkg in $(printf '%s' "$PACKAGES" | jq -r '.[]'); do apt-get install -y "$pkg"; done
4. Multiple invocations (array of option objects)
In the feature install layer (getFeatureLayers / getFeatureInstallWrapperScript and the
surrounding orchestration):
- When a Feature's value is an array of option objects, emit one install layer/wrapper per element,
in array order, each with its owndevcontainer-features.env. All invocations of a given Feature
run consecutively at that Feature's position in the install order (do not interleave with other
Features).
5. CLI flags
--override-features: already accepts a JSON blob; ensure array option values and array-of-objects
Feature values parse and flow through the new types.- Add/confirm a repeated-flag form:
--feature-option <feature>.<option> <value>appends to an
array-typed option (and sets/replaces for scalar options).
6. Validation & errors
- Reject non-array values for
array-typed options (after string coercion) with a clear error
naming the Feature and option. - Validate
enumelements; report the offending element.
Test plan
-
devcontainer-feature.jsonwithtype: "array"parses and surfacesdefault/enum/proposals. -
devcontainer.jsonwith"packages": ["curl","git"]reachesinstall.shas
PACKAGES='["curl","git"]'. - String
"curl,git"for anarrayoption coerces to["curl","git"]with a warning. - String
'["curl","git"]'(JSON) coerces to the array without warning. -
enum-constrained array option rejects an out-of-set element. - Array-of-option-objects Feature value invokes
install.shtwice with distinct env vars, in
order. - Repeated
--feature-optionflags append to an array option. - Existing comma-separated Features (e.g.
r-packages) continue to work unchanged after
migrating their option totype: "array". - JSON Schema (
devContainerFeature.schema.json) validates the new shapes.
Use cases
- Package lists —
r-packages,npm-packages,github-cliextensions, Homebrew formulae:
explicit arrays, values may contain commas. - Multiple runtime versions — install
.NET 3.1and6.0(or Node18+20) in one image
via array-of-option-objects, without bespoke per-Feature "multi-version" options. - Multi-select tool bundles — a Feature offering a curated subset of tools; with
enum
elements the UX can render a multi-select picker. - Tool-readable config — schema validation, IntelliSense, and diffs work natively on JSON
arrays; comma-strings are opaque to every tool except the splittinginstall.sh.
Prior art (alternative stacks)
The dev container spec is the only major dev-environment format lacking a native list type for
user-supplied option values:
| Stack | List input | Notes |
|---|---|---|
| Coder | coder_parameter type = "list(string)"; UI multi-select/tag-select; defaults via jsonencode([...]) |
First-class list type. Coder's docs warn that overriding list(string) on the CLI is "tricky" (CSV+JSON quoting) and offer a YAML workaround — exactly the ambiguity this CLI should avoid by defining array semantics up front. |
Nix (mkShell) |
Native lists packages = [ curl git jq ]; |
First-class; no string parsing. |
Gitpod (.gitpod.yml) |
Native YAML arrays (tasks, ports, vscode.extensions) |
First-class. |
| Docker Compose | Native YAML arrays (volumes, ports, environment) |
First-class. |
| Helm | Native YAML arrays in values.yaml, iterated with range |
First-class. |
| Terraform | list(string), list(any) native variable types |
First-class. |
| Dev Containers (this CLI) | ❌ No array option type — comma-separated string only |
Outlier. |
Dependencies & unblocking
- Blocked on spec acceptance: devcontainers/spec#766
(the RFC definingarrayoption type, Option Resolution for arrays, and array-of-option-objects). - Closes the long-standing workaround: spec#57
(2022) and spec#44.
Once the spec RFC lands, this issue is the implementation tracker for the reference CLI. The type
changes in §1 are the minimal unblocking step; everything else follows from the spec's normative
requirements.
References
- Spec RFC: https://github.com/devcontainers/spec/issues/766
- Original array-option request: https://github.com/devcontainers/spec/issues/57
- Multiple invocations: https://github.com/devcontainers/spec/issues/44
- Spec option resolution: https://containers.dev/implementors/features/#option-resolution
- Coder
list(string)parameters: https://coder.com/docs/admin/templates/extending-templates/parameters
- Dominant language
- TypeScript
- Stars
- 3k
- Forks
- 461
- Avg merge
- 18m
- Merged PRs (30d)
- 5
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from devcontainers/cli
-
Difficulty 1/5 Under an hour Newbie friendliness 92/100
devcontainers/cli#1203 ·
-
Difficulty 1/5 1-3 hours Newbie friendliness 68/100
devcontainers/cli#1178 · 1 comment ·
-
Difficulty 5/5 Over a week Newbie friendliness 25/100
devcontainers/cli#1308 ·
-
Difficulty 3/5 1-2 days Newbie friendliness 78/100
devcontainers/cli#1307 ·
-
Difficulty 4/5 3-5 days Newbie friendliness 55/100
devcontainers/cli#1305 ·
All issues in devcontainers/cli
Similar issues
-
clawsweeper:linked-pr-open clawsweeper:no-new-fix-pr clawsweeper:source-repro impact:message-loss issue-rating: 🦞 diamond lobster maturity:stable P2
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Eynzof/Hermes-CN-Desktop#616 ·
-
ZCode 3.14.3 に対応する Open
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
supermomonga/zcode-acp#24 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
growthbook/growthbook#7100 ·
-
triage
Difficulty 1/5 1-3 hours Newbie friendliness 88/100