Skip to content

[Bug]: devpod up aborts and never opens the IDE when a pre-attach lifecycle command (e.g. postCreateCommand) fails #772

Description

@YassineElbouchaibi

Before submitting

  • I have searched existing issues to confirm this is not a duplicate
  • I am running the latest version of DevPod

What happened?

When any pre-attach lifecycle commandonCreateCommand, updateContentCommand, postCreateCommand, or postStartCommand — exits non-zero, devpod up aborts with a fatal error and the IDE is never opened. Neither the in-container IDE server is installed, nor is the client launched (browser / VS Code Desktop / JetBrains Gateway / etc.). This affects all IDEs (vscode, vscode-insiders, openvscode, vscode-web, code-server, all JetBrains, fleet, jupyter, rstudio).

This diverges from the VS Code Dev Containers extension, which opens the editor and attaches to the container even when postCreateCommand fails, surfacing the error in a notification/terminal rather than aborting. From the maintainer in microsoft/vscode-remote-release#6206:

If the postCreateCommand has a non-zero return value the postStartCommand will never be called but the devcontainer will start up.

[chrmarti] We run these commands in the background while the VS Code window opens and starts accepting input. So it is too late to fail startup.

In practice this means a single failing setup step (a flaky npm install, a go: command not found, a typo in a script) leaves DevPod users without an editor at all — exactly when they most need one to inspect and fix the failure. With the Dev Containers extension they'd be dropped into the editor with the error visible.

Filing this for discussion (per the title it's framed as a bug, but it's partly a deliberate design point). The headless reference CLI @devcontainers/cli (devcontainer up) does exit non-zero on a lifecycle failure, the same as DevPod — so the divergence is specifically between DevPod and the editor/extension experience, not the headless CLI. See the comparison table below. The question is whether DevPod's interactive/--ide path should match the editor (open anyway + surface the error) rather than the headless CLI (abort).

What did you expect to happen instead?

The IDE should still open when a pre-attach lifecycle command fails, with the failure surfaced prominently (log/notification), matching the VS Code Dev Containers extension. The "a command failed" signal and the "open my editor" action should be decoupled.

At minimum this should be configurable (e.g. a flag / context option), since strict-abort is genuinely desirable for CI/scripting use of devpod up (where a non-zero exit is the point), while interactive/desktop use generally wants the editor to open so the user can debug.

There's a useful precedent in this fork: #728 ("Fixes #708") already split the hooks so that postAttachCommand no longer blocks the IDE (it now runs detached, after the IDE opens, with its error logged-but-non-fatal). The same reasoning arguably extends to the earlier hooks.

Steps to reproduce

Minimal, self-contained (no git repo needed). Using the docker provider:

mkdir -p /tmp/devpod-lifecycle-repro/.devcontainer
cat > /tmp/devpod-lifecycle-repro/.devcontainer/devcontainer.json <<'EOF'
{
  "name": "lifecycle-failure-repro",
  "image": "mcr.microsoft.com/devcontainers/base:debian",
  "postCreateCommand": "echo '>>> postCreateCommand is running; exiting non-zero on purpose' && exit 1"
}
EOF

# --open-ide=false keeps it non-interactive (no browser launch); the in-container
# IDE server install still demonstrates the gating. openvscode is used because it
# is headless-friendly, but the behavior is identical for every IDE.
devpod up /tmp/devpod-lifecycle-repro --ide openvscode --open-ide=false --provider docker
echo "exit code: $?"

Observed: devpod up exits 1 immediately after the failed postCreateCommand; the openvscode server is never installed, SSH is never configured, and no IDE is opened.

Control (only difference is the command's exit code): change postCreateCommand to a succeeding command, e.g. "echo '>>> ok'", recreate, and the run proceeds past the hook to Starting openvscode in background... + SSH configuration completed and exits 0. This isolates the failing exit code as the sole cause.

devcontainer.json

{
  "name": "lifecycle-failure-repro",
  "image": "mcr.microsoft.com/devcontainers/base:debian",
  "postCreateCommand": "echo '>>> postCreateCommand is running; exiting non-zero on purpose' && exit 1"
}

Error output / logs

Failing run (truncated to the relevant tail) — aborts at the hook, never reaches IDE install:

info  running postCreateCommands lifecycle hook:  echo '>>> postCreateCommand is running; exiting non-zero on purpose' && exit 1
info  >>> postCreateCommand is running; exiting non-zero on purpose
fatal run agent command failed: exit status 1: Process exited with status 1
lifecycle hooks pre-attach: failed to run: echo '>>> postCreateCommand is running; exiting non-zero on purpose' && exit 1, error: exit status 1
# exit code: 1

Control run with a succeeding command — does reach the IDE step:

info  running postCreateCommands lifecycle hook:  echo '>>> ok'
info  >>> ok
done  ran command: command=, args=echo '>>> ok'
info  Starting openvscode in background...
info  SSH configuration completed in workspace
# exit code: 0

(Reproduces the real-world report in #768, where a postCreateCommand that works in VS Code fails under devpod up.)

Root cause

Two short-circuits, both gating IDE opening on lifecycle success. Line numbers/permalinks at main (5db8020):

1. In-container agent sideinstallIDE is skipped when pre-attach hooks fail. cmd/agent/container/setup.go#L204-L210:

if err := setup.SetupContainerPreAttach(sctx.ctx, cfg); err != nil {
    return err   // <-- returns here on a failed onCreate/updateContent/postCreate/postStart
}

if err := cmd.installIDE(sctx.setupInfo, &sctx.workspaceInfo.IDE, sctx.logger); err != nil {
    return err   // <-- in-container IDE server install — unreachable on lifecycle failure
}

SetupContainerPreAttachRunPreAttachHooks runs onCreate/updateContent/postCreate/postStart and returns the first error (pkg/devcontainer/setup/setup.go#L59-L62, pkg/devcontainer/setup/lifecyclehooks.go#L55-L106); a hook errors when its command exits non-zero (cmd.Wait()).

2. Host side — the non-zero agent exit propagates back and Run returns before openIDE (the client-side browser / VS Code Desktop / JetBrains Gateway launch). cmd/up.go#L264-L276:

wctx, err := cmd.executeDevPodUp(ctx, devPodConfig, client, log)
if err != nil {
    return err   // <-- returns here; openIDE below is never reached
}
...
return cmd.openIDE(ctx, devPodConfig, client, wctx, log)

So both the in-container IDE server and the host-side editor launch are gated on every pre-attach hook succeeding.

History note: the "run setup → abort on error → then installIDE" ordering is inherited from loft-sh/devpod upstream. The fork's #728 only narrowed the gate so postAttachCommand no longer blocks the IDE; onCreate/updateContent/postCreate/postStart still do. (In current upstream loft-sh/devpod, even postAttachCommand still blocks the IDE.)

Comparison with reference implementations

Implementation postCreateCommand fails → does the editor/IDE open? Exit/outcome
DevPod (devpod up) ❌ No — aborts before IDE install/open (this issue) exit 1
VS Code Dev Containers extension ✅ Yes — window opens & attaches; error shown in notification/terminal (#6206) window opens
@devcontainers/cli (devcontainer up, headless) n/a (no editor) — throws ContainerError, outcome: error exit 1

The spec (containers.dev json_reference, lifecycle scripts) defines hook ordering but does not mandate IDE-open-on-failure semantics, so this is an implementation choice.

Proposal / options to discuss

  1. Match the editor (recommended for the --ide/desktop path): when a pre-attach hook fails, still open the IDE and surface the failure prominently (log line + desktop-app notification), like the Dev Containers extension. Optionally still exit non-zero so CI/scripting callers detect the failure — i.e. decouple "open editor" from "exit code."
  2. Make it configurable: a flag (e.g. --open-ide-on-failure) and/or context option, so interactive use opens the IDE while CI keeps strict-abort. Default TBD.
  3. Keep CLI strict, make the desktop app tolerant: the GUI (desktop/) opens the IDE regardless and shows the error; bare devpod up keeps current behavior.

Happy to put up a PR once we agree on the direction (likely reusing the pre/post-attach plumbing from #728).

How often does this happen?

Every time

Operating system

macOS

Architecture

ARM64

Desktop app or CLI?

CLI only

DevPod version

v0.25.1 (in-container agent v0.25.1; also present on current main)

DevPod provider

docker

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