Skip to content

[Bug]: Compose devcontainer customizations (extensions/settings) silently dropped when devcontainer.metadata label fails to parse #770

Description

@YassineElbouchaibi

What happened?

For Docker Compose–based devcontainers, customizations.vscode.extensions and customizations.vscode.settings are silently ignored — extensions are not installed and settings are not applied — when the generated devcontainer.metadata image label contains invalid JSON.

A quote-heavy lifecycle command (e.g. a postStartCommand using a single-quoted grep regex) is serialized into the devcontainer.metadata label. During the compose-override step, the label value is escaped such that every single quote ' is rewritten to \'\', which injects an illegal \' escape into the JSON. On the next up/attach, GetImageMetadataFromContainer calls json.Unmarshal, the unmarshal fails on the invalid escape, the error is logged but swallowed, and an empty metadata config is returned. MergeConfiguration then produces a merged config with no customizations, so the IDE setup step has nothing to install.

Observed CLI output:

error Error parsing image metadata: invalid character '\'' in string escape code

After this line the container starts normally, but none of the devcontainer.json extensions/settings are present in the editor.

What did you expect to happen instead?

customizations.vscode.extensions/settings declared in devcontainer.json should be applied regardless of quoting in lifecycle commands. The generated devcontainer.metadata label should always be valid JSON.

Steps to reproduce

  1. Create a Compose-based devcontainer (dockerComposeFile + service in devcontainer.json).
  2. Add a postStartCommand whose value contains an embedded single-quoted shell snippet (e.g. a quoted grep regex).
  3. Add customizations.vscode.extensions with one or more extensions.
  4. Run devpod up --ide <any web/desktop IDE>.
  5. Observe Error parsing image metadata: invalid character '\'' in string escape code and that none of the declared extensions are installed.

devcontainer.json

{
  "name": "compose-metadata-repro",
  "dockerComposeFile": "docker-compose.yml",
  "service": "app",
  "workspaceFolder": "/workspace",
  "postStartCommand": "grep -qE '\"key\"\\s*:' /etc/example.conf || true",
  "customizations": {
    "vscode": {
      "extensions": ["golang.go"]
    }
  }
}

Error output / logs

error Error parsing image metadata: invalid character '\'' in string escape code

Root cause

When writing the additional labels into the generated docker-compose override file, every single quote in the label value is rewritten to \'\'.

pkg/devcontainer/compose.go (in generateDockerComposeUpProject):

// Escape $ and ' to prevent substituting local environment variables!
label := regexp.MustCompile(`\$`).ReplaceAllString(v, "$$$$")
label = regexp.MustCompile(`'`).ReplaceAllString(label, `\'\'`)

This appears to be a mis-port of the devcontainers/cli reference, whose JavaScript replacement '\'\'' evaluates to '' (YAML single-quote doubling) — not the literal characters \'\'. Because devpod writes the override file with yaml.Marshal (which already handles YAML quoting), the manual single-quote escaping is unnecessary and corrupts the JSON. The $$$ escaping is correct and still needed for compose interpolation.

The resulting parse failure is then silently swallowed in pkg/devcontainer/metadata/metadata.go (GetImageMetadataFromContainer):

if err != nil {
    log.Errorf("Error parsing image metadata: %v", err)
    return &config.ImageMetadataConfig{}, nil
}

A self-contained round-trip (escape → yaml.Marshal → compose interpolation → json.Unmarshal) reproduces the exact error with the current escaping and parses cleanly once the single-quote rewrite is removed.

Impact

  • Affects all IDEs that rely on customizations (VS Code desktop, openvscode, code-server), not a single editor.
  • Specific to the Compose path; the non-compose build path bakes customizations into the image differently and is unaffected.
  • Silent: the only signal is one easily-missed log line; the container otherwise comes up fine, so it presents as "my extensions/settings aren't applied."

Suggested fix

  1. Stop rewriting single quotes when serializing the devcontainer.metadata label, so the label is always valid JSON (the real fix).
  2. (Defense in depth) Do not silently swallow the unmarshal error in GetImageMetadataFromContainer; surface it loudly and/or fall back to the parsed devcontainer.json customizations.
  3. Add a regression test asserting a quote-heavy postStartCommand plus customizations.vscode.extensions round-trips through the compose override with customizations retained.

Environment

  • DevPod: recent build
  • Devcontainer: Compose-based (dockerComposeFile + service)
  • OS: Linux
  • Provider: Docker / Podman

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions