Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Feature request: First-class array input for Feature options (array option type + multiple invocations) [blocked on spec#766]

Open
#1,298 0 comments 0 reactions 0 assignees View on GitHub

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
Domain
cli, devtools

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:

  1. array option typedevcontainer-feature.json may declare "type": "array" for an
    option, with default as an array and proposals/enum constraining elements.
  2. Array option values in devcontainer.json"packages": ["curl","git","jq"] must be
    accepted, validated, and propagated.
  3. Multiple invocations via array of option objects — a Feature value may be an array of option
    objects ("dotnet": [{"version":"3.1"},{"version":"6.0"}]), invoking install.sh once per
    element in order (resolves spec#44).
  4. Option Resolution for arrays — array values are serialized to devcontainer-features.env as
    a JSON array string (PACKAGES='["curl","git","jq"]'), the only delimiter-free, unambiguous
    encoding.
  5. 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 to type: "array".
  6. 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/proposals when present.
    • Default to [] when omitted and no default.
3. Option Resolution (env serialization)

In the code that writes devcontainer-features.env (<OPTION_NAME>=<value>):

  • For an array option, serialize the value with JSON.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.sh parses with jq (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 own devcontainer-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 enum elements; report the offending element.

Test plan

  • devcontainer-feature.json with type: "array" parses and surfaces default/enum/proposals.
  • devcontainer.json with "packages": ["curl","git"] reaches install.sh as
    PACKAGES='["curl","git"]'.
  • String "curl,git" for an array option 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.sh twice with distinct env vars, in
    order.
  • Repeated --feature-option flags append to an array option.
  • Existing comma-separated Features (e.g. r-packages) continue to work unchanged after
    migrating their option to type: "array".
  • JSON Schema (devContainerFeature.schema.json) validates the new shapes.

Use cases

  1. Package listsr-packages, npm-packages, github-cli extensions, Homebrew formulae:
    explicit arrays, values may contain commas.
  2. Multiple runtime versions — install .NET 3.1 and 6.0 (or Node 18 + 20) in one image
    via array-of-option-objects, without bespoke per-Feature "multi-version" options.
  3. Multi-select tool bundles — a Feature offering a curated subset of tools; with enum
    elements the UX can render a multi-select picker.
  4. Tool-readable config — schema validation, IntelliSense, and diffs work natively on JSON
    arrays; comma-strings are opaque to every tool except the splitting install.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 defining array option 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

Dominant language
TypeScript
Stars
3k
Forks
461
Avg merge
18m
Merged PRs (30d)
5

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from devcontainers/cli

All issues in devcontainers/cli

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.