Skip to content

[Feature]: Support interactive postStartCommand input #1569

Open

Description

@davzucky

Problem or motivation

Dev container projects may use postStartCommand for setup that asks the user for input. Devsy's current lifecycle execution path does not connect stdin or allocate a terminal, so users cannot answer those prompts.

Source inspection shows that executeAndCapture in pkg/devcontainer/setup/lifecyclehooks.go calls subprocess.Run without setting Options.Stdin. The helper in pkg/subprocess/capture.go assigns that unset value to cmd.Stdin and captures stdout/stderr without a PTY.

An example to validate is:

{
  "image": "mcr.microsoft.com/devcontainers/base:debian",
  "postStartCommand": [
    "bash",
    "-c",
    "read -r -p 'Name: ' name && echo \"Hello, $name\""
  ]
}

When starting the workspace from an interactive terminal, the user should be able to see the prompt, enter a name, and see the greeting. With stdin disconnected, this command cannot read the user's answer.

This request follows specification and source review; the example has not yet been run in a container.

Proposed solution

Support interactive postStartCommand execution when a user starts a workspace from an attached terminal. Forward user input, display command output as it runs, and provide a PTY when needed.

Define and document how this behaves when lifecycle hooks run in the background or no interactive terminal is attached, including through the desktop app. Account for the existing waitFor behavior and parallel object-form commands so prompts do not silently wait for inaccessible input.

Alternatives considered

  • Keep startup scripts noninteractive and supply configuration through arguments, environment variables, files, or secrets.
  • Run interactive setup manually after attaching. Moving the same prompt to postAttachCommand alone does not solve the missing input connection in the shared runner.

Area

CLI, Dev containers

Acceptance criteria

  • A user starting a workspace from an attached terminal can answer a prompt in postStartCommand and see its output.
  • Commands requiring a terminal can run through a supported PTY execution path.
  • Behavior is documented for background/headless execution and desktop launches.
  • Interaction with waitFor and parallel lifecycle commands is defined without silently waiting on input the user cannot provide.
  • Existing noninteractive hooks continue to work, with coverage for input forwarding and terminal handling.

Additional context

The Dev Container specification defines when postStartCommand runs and its command syntax. It neither prohibits interactive commands nor guarantees stdin or a terminal, so this is an implementation capability rather than a specification compliance requirement.

Activity

  1. skevetter commented on Oct 31, 2026

    @skevetter
    Contributor

    Implementation plan: attached-terminal input for postStartCommand

    Status: not implemented. The current hook runner still provides neither stdin nor a PTY. No matching implementation PR was found in searches for this issue and interactive post-start work. Keep this issue as the implementation tracker.

    Reviewed 30 October 2026 against main@9bc12ee041ca854c6f81de95fd8c6e49d7cc028c. The findings below come from source inspection; the interactive customer example has not been executed in this audit.

    1. Verified architecture and failure mechanism

    Current behavior Source Design consequence
    executeAndCapture calls subprocess.Run with working directory, environment and redactor, but no stdin. lifecyclehooks.go:614-659 Adding a local CLI stdin reader at this function is insufficient; it runs inside the container agent.
    The subprocess helper assigns its optional reader to cmd.Stdin and captures both output streams. capture.go Default execution must remain available unchanged. PTY execution needs a separate functional terminal path.
    The default wait boundary precedes post-start. Later hooks can run in a separate process. RunPreAttachHooks, worker launch An interactive request must keep post-start in the foreground for that invocation. Passing stdin to a detached worker cannot supply an attached user.
    Object-form named commands run concurrently. executeLifecycleHook A single terminal must never be shared between competing prompt readers.
    A hook's marker is checked/created before execution. runHook Reject known unsupported interactive requests before this point; preserve existing attempt/skip semantics after execution begins.
    CLI-to-host and host-to-container setup use gRPC over the existing stdio/SSH streams. outer up server, inner setup server Raw terminal bytes cannot be copied into agent stdin/stdout. Those streams already carry a protocol.
    The tunnel schema has no terminal RPC. tunnel.proto Add an additive typed stream with explicit ownership, rather than hiding input inside log/status messages.
    pkg/pty already starts PTYs, exposes input/output, resizes and waits. Existing SSH code owns/restores host terminal modes. PTY interfaces, PTY startup, host terminal setup Reuse these foundations. Do not add another terminal emulator or shell transport.
    --terminal-columns and --terminal-rows are already registered for lifecycle scripts. up_flags.go:96-112 Wire their existing fields into the new terminal contract.
    Desktop submits workspace up --detach and follows task logs. ipc.ts:2113-2166 Its current launch path has no terminal owner. Define this explicitly; do not make the log viewer an input channel.
    Platform daemon clients create and wait for management tasks. daemonclient/up.go They cannot implicitly inherit an attached CLI terminal. Reject this new mode until that execution route has a terminal bridge.

    The Dev Container lifecycle specification defines phase ordering, command forms and waitFor; it does not promise stdin or a TTY. The reference CLI does request a PTY for lifecycle execution. This is a supported execution capability, not a reason to change the devcontainer schema.

    2. Product behavior and bounded first-release scope

    Add one explicit CLI option:

    devsy workspace up ./project --interactive-post-start --ide none

    The option connects the attached terminal to postStartCommand in the normal startup sequence. It does not replay hooks after a completed launch. It supports string, argv-array and single-named-command forms, including sequential merged post-start hooks. No new property is required in devcontainer.json.

    Use opt-in behavior because automatic input on every TTY invocation would change existing automation and the default foreground/deferred split.

    Mode or condition Required behavior
    Option absent Existing capture-only execution, stdin behavior, scheduling and named-command parallelism remain unchanged.
    Option present with attached stdin/stdout terminals Validate the request, reserve a terminal session, and run eligible post-start commands in a container PTY. Show prompts immediately, including prompts without a newline.
    Default or earlier configured waitFor Raise the effective invocation floor to postStartCommand using the existing PreAttachOptions.WaitFor mechanism. Earlier phases still run in order. Do not edit or persist a different project waitFor.
    A later configured waitFor Preserve it. This option grants input only to post-start; it does not make post-attach interactive.
    --detach, detached worker, headless CI/API, prebuild, recovery, --skip-post-start, or --skip-non-blocking-commands together with this option Return a specific incompatible-mode error before task submission or provider/container work. Do not silently ignore either option. An environment variable named CI alone is not a capability test; the attached-PTY acceptance harness may run inside CI.
    JSON result output, DEVSY_UI=true, or missing terminal stdin/stdout Return a specific attached-terminal requirement error. Keep stdout machine-readable when the command selected JSON.
    Classic workspace client over local Docker/Podman/Compose, Kubernetes, or SSH/cloud host Support through the same existing two-hop setup tunnel when both agents advertise the new protocol. Do not rely on a Docker-only -t flag.
    Proxy/management daemon client or platform-managed task route Fail with an unsupported-execution-route error before starting its task. Extending these routes is separate work, not an implicit fallback.
    More than one nonempty named command in any merged post-start object Reject interactive mode before container lifecycle execution, naming the phase and object index. Preserve parallel behavior when interactive mode is absent. Do not serialize the object or attach multiple readers to stdin.
    No post-start command, or an existing marker skips it Preserve the normal no-op/skip result and explain that no command ran. Never clear a marker to obtain a prompt.
    Missing PTY, old peer or unavailable terminal bridge Fail with a clear capability error before launching post-start. Do not fall back to disconnected input and claim interactive execution succeeded.
    Terminal closes or the launch is canceled End the selected hook session, terminate its owned process group, drain bounded remaining output, restore the local terminal, and return a failure/cancellation. Do not move that hook to a background worker.

    The Desktop app keeps its current detached launch behavior. Document that the first release's interactive create/start is performed in an attached CLI terminal, after which the workspace can be opened from Desktop. Existing Desktop launches remain noninteractive. Adding a persistent interactive setup panel would require a separate UI/session ownership design; it is not needed to satisfy this CLI capability.

    There is no generic way to identify every program that expects input before running it. The guarantee is that an explicitly requested interactive session is either available or rejected. The ordinary noninteractive path keeps its existing stdin behavior; do not invent a heuristic that recognizes prompts by scanning output.

    3. Terminal stream contract

    Add a bidirectional LifecycleTerminal RPC to pkg/agent/tunnel/tunnel.proto, with generated Go and gRPC files. Use distinct request/response messages with oneof frame types.

    Identity and ownership

    • The initiating CLI allocates a fresh invocation nonce after workspace resolution and binds it to the current workspace UID and originating context. Carry the complete ephemeral terminal request, including protocol version, nonce, UID and originating context, through both handoffs. ContainerWorkspaceInfo currently has no UID/context and Workspace.Context is excluded from JSON, so these values cannot be reconstructed inside the container from the existing serialized types. Do not substitute the runner ID, which can be a legacy workspace ID, for UID. Runtime terminal readers/writers remain process-local.
    • The host endpoint is enabled only by this explicit CLI option. An absent owner returns FailedPrecondition; a wrong nonce/workspace returns PermissionDenied. The session cannot ask the CLI to execute a command.
    • The inner container stream reserves one session before lifecycle execution. It may run multiple sequential post-start invocations, identified by a per-command ID plus merged-hook index/named key. Permit one active invocation. Reject a second simultaneous session or begin frame.
    • Clear transient request fields when preparing a fresh invocation and from long-lived persisted options/results. A later Desktop/headless start cannot inherit an interactive request. Never reconstruct terminal access from a cached result, task log or persisted nonce.

    Frames and ordering

    Direction Frame Required contents and behavior
    Container to CLI open Protocol version 1, workspace UID/context, invocation nonce. Must be first.
    CLI to container ready Accepted version, sanitized TERM, initial rows/columns and input capability. Reservation does not enter raw mode or begin reading stdin.
    Container to CLI begin Command invocation ID, phase fixed to postStartCommand, merged index and optional named key. Labels are data, never executable text.
    CLI to container beginAck Confirms terminal modes and ownership are established. Only then start the PTY command.
    Container to CLI output Literal PTY bytes, invocation ID and increasing sequence. PTY output is one combined stream; do not manufacture stdout/stderr provenance.
    CLI to container input Raw terminal bytes for the active invocation only. Never log, persist, echo independently, or replay them.
    CLI to container resize Positive bounded rows/columns for the active invocation. Coalesce redundant queued sizes.
    CLI to container cancel Terminate the current owned invocation and fail the session. Not a command to stop/delete the workspace.
    Container to CLI exit Actual exit code or signal, or explicit start failure; sent after the output reader has drained.
    CLI to container exitAck Confirms output delivery and terminal restoration. Container waits for this before returning the hook result or starting the next invocation.
    Container to CLI complete No further invocations. Close the reserved session; normal result reporting remains the existing result RPC.

    Handshake and acknowledgements have five-second bounds. The active input wait has no arbitrary user-thinking timeout. Parent context cancellation and broken transport end the session. Unexpected EOF of the host input reader is a disconnect/cancel condition. Ctrl-D is ordinary terminal input, handled by the PTY line discipline; do not interpret it as a gRPC half-close.

    Cap each data frame at 32 KiB and queued data in either direction at 256 KiB. Use flow control/backpressure rather than dropping input or prompt bytes. Limit textual identities and TERM to bounded lengths, validate enum values and sequence/state transitions, and reject oversized/out-of-order frames. Do not retain the entire transcript in memory. Close and join all owned read/write goroutines on every terminal path.

    Two-hop forwarding

    The container agent connects to the provider-host setup server; that server forwards the stream over its existing upstream tunnel client to the initiating CLI. Neither hop consumes its own process stdin as user input. The outer agent stdio remains gRPC/SSH protocol traffic, and terminal frames never enter Log, Status, SendResult or the Desktop envelope parser.

    4. File-level implementation tasks

    A1. Add the explicit option and fail before side effects

    • pkg/flags/names/names.go: add the canonical interactive-post-start name and its name test.
    • cmd/workspace/up/up_flags.go:registerLifecycleFlags: register the boolean, default false. Reuse existing terminal dimensions rather than adding width/height aliases.
    • cmd/workspace/up/up.go, up_validate.go, detach.go: validate flag conflicts before runDetached can submit a task and before prepareClient can start provider work. Validate again in public non-Cobra/headless entry points. Accept only attached stdin/stdout and resolved plain result presentation. Do not turn JSON into plain output silently.
    • cmd/workspace/up/agent.go:dispatchClient: reject the explicit mode for proxy/daemon/platform task routes before their Up call. For the supported classic workspace route, create and own the terminal endpoint for this one invocation.
    • pkg/provider/workspace.go:CLIOptions: add an optional typed invocation request containing interactive intent, protocol version, nonce, originating context and workspace UID, with omitempty. Keep terminal handles and cancellation functions out of this serializable type. Audit copies in up_client.go, buildWorkspaceOptions, withoutSecretValues and setup compression so the complete request survives only the current handoff. Both forwarding endpoints compare it with their bound invocation. pkg/agent/agent.go:writeWorkspaceInfo already clears the cloned CLIOptions before persistence; preserve that invariant and test it. compressWorkspaceConfig must still carry the live invocation into setup, without saving it as a later default.
    • Introduce specific errors for unavailable terminal, unsupported route, incompatible flags and parallel interactive post-start. Route them through existing CLI error formatting with actionable hints; keep status envelope version 1 unchanged.
    • Add a mandatory internal --lifecycle-terminal-protocol=1 argument to the workspace-agent and container-setup commands only when this mode is requested. Register and validate it in cmd/internal/agentworkspace/up.go and cmd/internal/agentcontainer/setup.go, checking it agrees with the serialized intent. Older agents must reject this unknown internal flag before executing hooks; silently ignoring an unfamiliar JSON field is not a capability check.

    A2. Implement the additive transport and host owner

    • pkg/agent/tunnel/tunnel.proto and generated files: implement the typed contract above without changing existing field numbers or RPC semantics.
    • New pkg/agent/tunnelserver/lifecycle_terminal.go: implement the server adapter and bounded forwarding adapter. Add focused options in options.go for a local terminal owner or an upstream tunnel client. An unconfigured server explicitly rejects the new RPC.
    • cmd/workspace/up/agent.go:devsyUpMachineSSH: pass the terminal owner to the outer RunUpServer; append the internal protocol guard to the generated agent-workspace command with the existing shell-quoting convention.
    • cmd/internal/agentworkspace/up.go:devsyUp: pass its existing tunnelClient through a runtime-only field in devcontainer.UpOptions. In pkg/devcontainer/run.go, retain that field only for the current Up call and clear it on exit; do not serialize it.
    • pkg/devcontainer/setup.go:executeSetup: add the terminal forwarding option to RunSetupServer using that upstream client. In buildSetupCommand, add the internal protocol guard for interactive requests. Preserve the existing raw transport streams and startup watchdog.
    • New cmd/workspace/up/interactive_post_start.go: own the local session state, one stdin reader, raw-mode entry/restoration, output writer, resize forwarding and cancellation. Reuse pkg/pty.MakeInputRaw, MakeOutputRaw, RestoreTerminal, and pkg/ssh.WatchWindowSize behavior already used by machine SSH.
    • Add a cancellable, owned terminal-input adapter under pkg/terminal for Unix and Windows. Use an owned descriptor/handle and cancellation signaling with the existing OS dependencies; closing it must unblock its read without closing caller-owned os.Stdin. Native tests must establish that no goroutine remains blocked and no old reader consumes input after the session ends.
    • Use positive --terminal-rows/--terminal-columns as explicit requested dimensions; zero means automatic. Reject negative or greater-than-65535 values. Auto dimensions follow terminal resize; explicit dimensions remain fixed. Use 80x24 only when a genuinely attached terminal cannot report a size. TERM defaults to xterm-256color when absent and is passed as one environment value.
    • Gate only human progress rendering while a post-start terminal invocation owns the console, before the existing journal tee. Continue producing/journaling status events. Restore the presenter after exit, including failures. Respect merged --no-status behavior: it must never suppress terminal bytes. Existing logger diagnostics remain on their diagnostic stream.

    A3. Integrate scheduling and prevent duplicate execution

    • cmd/internal/agentcontainer/setup.go:finalizeSetup: create the bounded stream adapter from the current setup tunnel. Validate all merged post-start hook shapes and reserve the terminal before SetupContainerPreAttach can run lifecycle hooks or create their markers.
    • pkg/devcontainer/setup/setup.go:ContainerSetupConfig and lifecyclehooks.go:PreAttachOptions: add a process-local interactive executor/session reference. When enabled, raise the current WaitFor floor to PhasePostStart using resolveWaitFor. Leave the stored MergedConfig.WaitFor unchanged.
    • Add a focused validateInteractivePostStart helper that checks every merged post-start entry. Permit zero/one nonempty named command per entry; reject a multi-command object before running any container lifecycle phase. Include merged index/key in the error and preserve ordinary map parallelism.
    • In preAttachPhaseParams and runSingleHookCommand, attach the interactive executor only to post-start. Other phases keep their existing executor and environment resolution.
    • Preserve shouldSkipHook and ContainerDetails.State.StartedAt marker behavior. A normal reopen of the same running container must not prompt again. A normal stop/start produces a new start identity and runs post-start once. Transport loss does not authorize deleting a marker or automatically retrying a possibly non-idempotent hook.
    • setupPostAttach, buildDeferredHooksCmd, deferred_hooks.go and post_attach.go: never carry a terminal owner or nonce into detached workers. Post-start is already consumed by the foreground floor. Preserve worker readiness/activity protection, including the recent Kubernetes inactivity ownership changes.
    • An invalid request is rejected before markers. A failure after the existing attempt marker has been created retains the existing attempted-hook policy and reports that failure. Do not label an old marker as proof that interactive setup succeeded.

    A4. Run the selected hook in the existing PTY implementation

    • Add pkg/devcontainer/setup/lifecycle_terminal.go for the interactive executor. Factor command interpretation from the user-switch wrapper in buildCommandArgs, retaining its string/argv quoting and shell-selection semantics, resolved remote user, workspace directory and lifecycle environment. Do not execute a reconstructed display string. Keep the normal noninteractive runner unchanged.
    • Use a lifecycle-specific same-session user launch for interactive commands. The current different-user path is su <user> -c ...; util-linux documents that this creates a new session without a controlling terminal. Wrapping it unchanged in a PTY does not establish the proposed foreground-signal/process-group contract. Resolve the target UID, primary GID and supplementary groups, and apply syscall.Credential before the PTY child starts when switching identities is required. Factor reusable credential-resolution logic from pkg/ssh/server/exec_identity_unix.go if appropriate; do not call its whole prepareExecIdentity helper, which also changes the working directory and environment. When identity already matches, retain the rootless path without unnecessary credential syscalls. Preserve the target-account shell behavior currently selected by su for a different user, and preserve the current same-user command interpretation. Verify HOME/USER/LOGNAME/SHELL, remoteEnv precedence, workspace directory and supplementary groups against the existing lifecycle contract. Direct credential execution does not invoke PAM; document that opt-in compatibility boundary rather than promising all implementation-specific su/PAM side effects. Resolve known identity/shell errors before the lifecycle marker and never silently run as the setup agent's user.
    • Preserve inherited environment plus resolved hook overrides before adding TERM. Keep the existing remoteEnv, workspace-env, user probe and secret resolution behavior. A value containing = must remain intact.
    • Reserve/acknowledge the host terminal and install the initial dimensions before the child starts. Reuse pty.WithPTYOption(pty.WithSSHRequest(...)) with the accepted initial window, as pkg/ssh/server/exec.go does; pkg/pty/ssh_other.go:applyTerminalModesToFd applies that window during allocation. A first Resize after pty.Start races a fast command's initial terminal-size read. Then pump literal combined output to the bounded stream, forward input to InputWriter, and apply later validated resize events via Resize.
    • Keep a separate bounded redacted diagnostic tail using the existing subprocess buffer/redactor primitives. Factor the common hook-failure formatting from executeAndCapture so ordinary and PTY runners produce consistent phase/key/exit context.
    • Functional live terminal output goes directly to the attached user's terminal, without logger filtering or a suffix-holding redactor that could hide a prompt until after input. Diagnostic copies are separately redacted. Never log or retain input frames; do not double-echo them. PTY line discipline controls command echo and password prompts.
    • The current pty.Process.Signal/Kill addresses the direct process, and pty.Cmd.AsExec uses exec.CommandContext, whose default cancellation immediately kills that child. Add a lifecycle-only PTY start/cancellation option that replaces exec.Cmd.Cancel before Start and records the newly created process group. The same owner handles parent-context cancellation and transport disconnect exactly once: send SIGTERM to the owned group, allow two seconds for exit, then SIGKILL the remaining owned group. Do not leave the default direct-child kill racing this grace period. Keep the child attached to the parent's cancellation lifetime and preserve the original context error. Do not signal container PID 1 or unrelated processes, and leave ordinary SSH PTY startup/cancellation unchanged. The guarantee covers the owned process group; explicitly self-daemonizing commands that escape it are outside this terminal-session contract.
    • Drain output before the exit frame; bound cleanup when a descendant holds a descriptor open. Join pumps, close PTY and stream exactly once, and preserve the original exit/start/cancellation error. Cleanup errors may add context but must not turn a failed hook into success.
    • Do not allocate a PTY for noninteractive hooks or globally change subprocess.Options.Stdout, which is an existing functional/protocol sink.

    A5. Documentation and Desktop compatibility

    • Update sites/docs-devsy-sh/content/docs/developing-in-workspaces/create-a-workspace.mdx and devcontainer-json.mdx with the new option, the issue's read example, effective wait boundary, supported command forms, normal restart versus same-start reopen, and attached-terminal requirement.
    • Update continuous-integration.mdx and troubleshooting documentation to state that headless CI, Desktop detached launches and management tasks remain noninteractive. Offer argument/environment/secret-based setup as the existing unattended path. Moving the prompt to post-attach alone does not supply input.
    • Keep Desktop task/status parsing unchanged. Add a main-process integration assertion that an attempted interactive flag combined with detached launch yields the actionable CLI error and never starts a hidden prompt. Do not inject raw terminal bytes into workspace task logs.
    • Coordinate with #1735: any retained diagnostic copy of PTY output uses combined provenance and the same redaction/bounds as other retained output. Input frames are excluded. This capability does not depend on log retention being finished and does not rerun commands when logs are opened.

    5. Validation and acceptance

    Use signed Draft PRs and the existing CI suites. Tests need to cross the process and transport boundaries that cause the defect.

    Test group Required observable assertions
    CLI validation Interactive flag plus detach/JSON/headless API/prebuild/recovery/skip conflict fails before task/provider side effects; missing TTY/headless CI fails; a real attached PTY is not rejected merely because its test runner sets CI; default invocation remains unchanged.
    Internal old-agent guard Both old workspace-agent and old container-setup command parsers reject the new required internal protocol flag before any lifecycle marker/command is run. Unimplemented RPC is a clear capability failure, not fallback.
    Scheduler Effective floor reaches post-start while persisted waitFor is unchanged; later waitFor preserved; post-start never reaches the deferred worker; same-start marker skips; stop/start runs once.
    Command forms String, argv and single-key object work as the resolved remote user in the correct directory. Multiple merged single-command entries run sequentially. Multi-key object rejection precedes earlier lifecycle hooks; ordinary multi-key objects still run concurrently.
    Real PTY Command observes test -t 0 and test -t 1, can open /dev/tty, prints a prompt without newline, receives a supplied answer, and writes a success marker. Verify the very first size read and live resize. Run same-user/rootless and different named-remoteUser cases, checking shell/environment, working directory and supplementary groups.
    Stream contract Wrong nonce/UID, duplicate owner, parallel begin, oversized/out-of-order frames and use after exit are rejected; queues stay bounded; backpressure preserves exact bytes.
    Two-hop setup Exercise CLI to provider-host to container with both forwarders, while status/result/credential RPCs remain valid. Raw terminal text resembling JSON status envelopes stays terminal data.
    Failure and ownership Hook exit 7 remains a failed hook; Ctrl-C reaches the PTY foreground process for same-user and switched-user execution; disconnected client cancels owned process-group children. A SIGTERM trap gets the grace period, a stubborn child is killed after it, and default exec.CommandContext cancellation does not bypass that sequence. PTY-start failure restores host modes; no blocked stdin reader survives.
    Data handling Input bytes never appear in diagnostic logs or retained records; known secrets in diagnostic output are redacted across split writes; live no-newline prompts are visible immediately; quiet/no-status still permits functional terminal output.
    Existing behavior Existing lifecycle, secret, user-env-probe, marker, waitFor, parallel-hook, deferred-worker and managed-inactivity tests pass. Desktop detached task JSON remains parseable.

    Extend pkg/devcontainer/setup/lifecyclehooks_test.go and add focused lifecycle_terminal_test.go. Use existing pkg/pty/ptytest for process-level terminal assertions; add actual Unix/Windows local-input ownership tests rather than relying only on mock readers. Transport tests belong beside pkg/agent/tunnelserver with a real bounded gRPC stream and two-hop forwarding fixture.

    In e2e/tests/up, add a fixture under testdata/interactive-post-start/.devcontainer and a focused Ginkgo test that launches the actual CLI inside a PTY, waits for the prompt, writes an answer, and verifies the marker through a separate normal workspace command. Run this on the existing Docker CI leg, then one SSH-host route and one Kubernetes route. Use the existing suite manifest in hack/ci/suites.yaml; a new label must map to an executable job. Include a Compose case because lifecycle/config merging follows a separate container-start route. Native Windows/macOS tests must prove host terminal restoration and input-reader cancellation; a Linux-only fake is not that evidence.

    Customer completion flow: from an attached terminal, start the issue's devcontainer with --interactive-post-start, see Name:, enter a name, see the greeting and reach workspace-ready. Confirm normal reopen does not repeat the same start hook, then stop/start and answer once again. Confirm an unattended/desktop launch does not acquire an invisible input session. The issue is complete when this flow and the stated transport/compatibility cases pass in the published CLI/Desktop bundle version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions