Before submitting
What happened?
When any pre-attach lifecycle command — onCreateCommand, 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 side — installIDE 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
}
SetupContainerPreAttach → RunPreAttachHooks 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
- 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."
- 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.
- 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
Before submitting
What happened?
When any pre-attach lifecycle command —
onCreateCommand,updateContentCommand,postCreateCommand, orpostStartCommand— exits non-zero,devpod upaborts 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
postCreateCommandfails, surfacing the error in a notification/terminal rather than aborting. From the maintainer in microsoft/vscode-remote-release#6206:In practice this means a single failing setup step (a flaky
npm install, ago: 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.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
postAttachCommandno 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
dockerprovider:Observed:
devpod upexits1immediately after the failedpostCreateCommand; 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
postCreateCommandto a succeeding command, e.g."echo '>>> ok'", recreate, and the run proceeds past the hook toStarting openvscode in background...+SSH configuration completedand exits0. 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:
Control run with a succeeding command — does reach the IDE step:
(Reproduces the real-world report in #768, where a
postCreateCommandthat works in VS Code fails underdevpod up.)Root cause
Two short-circuits, both gating IDE opening on lifecycle success. Line numbers/permalinks at
main(5db8020):1. In-container agent side —
installIDEis skipped when pre-attach hooks fail.cmd/agent/container/setup.go#L204-L210:SetupContainerPreAttach→RunPreAttachHooksrunsonCreate/updateContent/postCreate/postStartand 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
Runreturns beforeopenIDE(the client-side browser / VS Code Desktop / JetBrains Gateway launch).cmd/up.go#L264-L276:So both the in-container IDE server and the host-side editor launch are gated on every pre-attach hook succeeding.
Comparison with reference implementations
postCreateCommandfails → does the editor/IDE open?devpod up)1@devcontainers/cli(devcontainer up, headless)ContainerError,outcome: error1The 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
--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."--open-ide-on-failure) and/or context option, so interactive use opens the IDE while CI keeps strict-abort. Default TBD.desktop/) opens the IDE regardless and shows the error; baredevpod upkeeps 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 currentmain)DevPod provider
docker