feat: implement typed lifecycle command format support#13
Merged
Conversation
Adds support for three lifecycle command formats per DevContainer spec: - String: shell-interpreted (/bin/sh -c in container, sh -c on host) - Array: exec-style, passed directly to OS without shell wrapping - Object: named parallel commands, each value is Shell or Exec ## Core Changes - Introduce LifecycleCommandValue enum (Shell, Exec, Parallel variants) - Implement format-aware parsing with spec-compliant validation - Wire typed commands through aggregation and execution pipeline - Support variable substitution element-wise for arrays, recursively for objects - Implement parallel execution via tokio::JoinSet with error aggregation - Capture stdout/stderr in parallel execution for diagnostics - Add output attribution via [key] prefixes for parallel commands ## Fixes & Quality Improvements - Replace 4 unreachable!() in runtime paths with fallible DeaconError - Return validation errors for invalid array entries in object format - Filter empty strings/arrays as no-ops at parse time - Document serde_json preserve_order dependency - Extract duplicate workspace folder derivation to shared helper - Log warnings on poisoned progress tracker mutex - Use sentinel values in dummy container config - Add concurrency tests verifying parallel execution is truly concurrent - Add comprehensive format × phase integration tests ## Limitations - run-user-commands: feature lifecycle commands not yet aggregated (documented gap) - Parallel output prefixing requires inherited stdio (captured in CommandResult.stdout/stderr but not line-prefixed to terminal) Fixes issues from spec review: format parsing, exec semantics, parallel execution, variable substitution, all six lifecycle phases, empty/null handling. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
3 tasks
pofallon
added a commit
that referenced
this pull request
May 26, 2026
…ONTRIBUTING (#47) Bundles three P2 polish items from the post-1.0 audit: ## SECURITY.md (gap #10 policy half) New top-level file documenting: - Supported versions - Private reporting channels (GitHub Security Advisory + email) - Scope (in-scope: command injection, path traversal, secret leakage, TLS/OCI auth, runtime privilege escalation; out-of-scope: upstream Docker/Podman bugs, third-party features, CLI-process DoS) - Coordinated disclosure window - Pointer to the CI security gates ## CodeQL workflow (gap #10 scanning half) New `.github/workflows/codeql.yml`: Rust analysis on PR + push to main + weekly schedule (Monday 06:00 UTC, staggered from cargo-deny's 07:00 UTC daily). Uses the workspace MSRV. Builds `--workspace --all-targets` so the full subcommand surface is analyzed. ## Cargo manifest metadata (gap #12) Added to `[workspace.package]` and per-package `[package]`: - `homepage`, `documentation`, `keywords`, `categories` - Per-package `description` and `readme` for crates.io rendering This unblocks publishing both crates to crates.io with proper search discoverability. ## CONTRIBUTING.md refresh (gap #13) The Quick Start and Common Tasks sections centered on raw `cargo test`, but the repo standardized on `cargo-nextest` + `make` targets long ago. Updated to: - Lead with `make dev-fast` / `make test-nextest-fast` in the Quick Start - Replace the Common Tasks table with the make-target taxonomy - Drop the long e2e-by-name list (replaced with general "filter by name" guidance — the e2e suite has grown past the original 7 named tests) - CI/CD section now reflects the actual workflows on main: ci.yml + codeql.yml + cargo-deny, with Conventional-Commits PR-title gate. ## Verification - `cargo build` ✓ - `cargo fmt --all -- --check` ✓ Refs: gaps #10, #12, #13 from the post-1.0 audit. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements full support for three lifecycle command formats per the DevContainer specification:
/bin/sh -cin container,sh -con host)What's Changed
Core Type System
LifecycleCommandValueenum withShell(String),Exec(Vec<String>),Parallel(IndexMap<String, LifecycleCommandValue>)variantsfrom_json_value()with type validationExecution
tokio::JoinSet— all entries run concurrently, all complete before phase result reported (no early cancellation per spec)[key]prefixes in progress events per entryCode Quality Fixes
unreachable!()calls in runtime paths with properDeaconError::Lifecyclereturnsserde_jsonpreserve_orderdependency for object key orderingshared::derive_container_workspace_folder()warn!on poisoned progress tracker mutex instead of silent swallowinitializeCommandfromrun-user-commands(host-only, belongs touponly)run-user-commandswith TODOTests
Known Limitations
CommandResult.stdout/stderrbut not line-prefixed to terminal in real-time (inherited stdio model constraint).Spec Compliance
✓ All 3 formats work for all 6 lifecycle phases
✓ Exec-style arrays bypass shell — arguments passed literally
✓ Parallel objects run concurrently, wait for all, aggregate failures
✓ Variable substitution preserves structure (element-wise / recursive)
✓ Empty/null commands filtered as no-ops at every level
✓ Per-entry progress events with
{phase}-{key}command IDs🤖 Generated with Claude Code