Skip to content

Restore color and clearer nested output for orchestrated commands #239

@coisa

Description

@coisa

Problem

The PTY fix made orchestrated commands safer in non-interactive environments, but it also removed a useful side effect: child commands no longer reliably emit colored output when DevTools is run from an interactive console.

There is a second usability issue in the same surface. Aggregate commands such as composer dev-tools, composer dev-tools standards, composer dev-tools reports, and other commands that use ProcessQueue log a DevTools message, stream the child process output directly, and then immediately log the next command. Outside GitHub Actions grouping, that output can feel visually glued together and makes it hard to see which nested command a given block belongs to.

Current Behavior

  • ProcessQueue streams child process output through Symfony Process callbacks without enabling PTY, which is correct for CI stability.
  • GitHub Actions output can be grouped through GithubActionOutput::group(...).
  • Local console output does not get an equivalent visible section boundary.
  • FORCE_COLOR=1 is configured by several workflows for the top-level DevTools process, but the nested Symfony Process instances do not consistently receive a color-friendly environment.
  • Tools that decide ANSI support from TTY detection or environment variables may disable color once PTY is no longer used.

Expected Behavior

  • Orchestrated commands should keep colored output in interactive terminals without reintroducing PTY-dependent failures.
  • GitHub Actions runs should also preserve color where the underlying tool supports forced ANSI output.
  • Local console output should clearly show when a nested command starts and ends, with readable spacing or section boundaries around each child process.
  • JSON and agent-oriented output modes should remain machine-readable and should not receive decorative local section chrome.

Failure Surface

Likely affected surfaces include:

  • src/Process/ProcessQueue.php
  • src/Process/ProcessBuilder.php
  • aggregate commands that enqueue subprocesses, including standards, reports, docs/phpdoc, metrics, tests, code-style, refactor, wiki, dependencies, and sync flows where applicable
  • workflow environments that currently set FORCE_COLOR=1 only for the top-level command
  • docs that describe JSON/pretty JSON output and command orchestration behavior

Proposed Strategy

Keep the non-PTY model and make color/section behavior explicit instead of relying on terminal inheritance.

1. Propagate a color-friendly environment to child processes

When ProcessBuilder or ProcessQueue creates/runs nested Process instances, ensure the process receives the relevant environment from the current runtime. At minimum evaluate propagating or setting:

  • FORCE_COLOR=1 when the parent output is decorated or when GitHub Actions already sets it;
  • CLICOLOR_FORCE=1 for tools that honor it;
  • NO_COLOR as an opt-out that must not be overridden;
  • TERM when present and useful;
  • any existing command-specific env already set by callers.

The important rule is: do not restore PTY just to get colors. Colors should be forced through environment and explicit tool flags where available.

2. Keep tool-specific ANSI flags where they exist

Some tools support explicit ANSI flags or output configuration. Audit whether we should add or preserve flags such as:

  • Symfony Console --ansi for nested composer dev-tools ... calls when the parent output is decorated;
  • PHPUnit color-related flags when not in JSON/coverage-only machine mode;
  • Rector/ECS/PHP-CS-Fixer/phpDocumentor behavior under FORCE_COLOR and non-TTY output.

Prefer central propagation first, then add per-tool flags only where the tool ignores standard color env variables.

3. Improve local nested command boundaries

GithubActionOutput::group(...) already gives CI a clear grouping primitive. For local consoles, add an equivalent presentation layer around ProcessQueue labels, for example:

  • use Symfony Console SymfonyStyle sections or ProcessHelper rendering where it gives a clear command boundary;
  • or add a small DevTools-owned ProcessOutputSectionRenderer that writes a blank line, a styled heading such as ▶ Running ..., streams output, and writes a closing newline/status line when useful;
  • suppress or simplify this chrome when --json, --pretty-json, or an agent-detected JSON mode is active.

The local renderer should avoid making output noisy, but it should make nested command ownership obvious.

4. Preserve stderr routing and GitHub Actions grouping

Any implementation must keep the current stdout/stderr separation and must not break GithubActionOutput::group(...) in CI. The local grouping behavior should complement the GitHub Actions grouping behavior, not replace it.

Implementation Notes

  • ProcessQueue is the best central point for runtime behavior because all orchestrated child processes flow through it.
  • ProcessBuilder may be the right place for default env assembly if we want built Process instances to carry inherited color env before they are enqueued.
  • Avoid literal process objects in tests except where impossible; tests should mock Process, OutputInterface, ConsoleOutputInterface, and any new renderer/capability abstractions.
  • Add tests that assert child processes receive the expected env or that Process::setEnv()/equivalent behavior is invoked without enabling PTY.
  • Add tests for local output boundaries without requiring real subprocess execution.
  • Verify behavior both with decorated console output and with JSON output flags.

Non-goals

  • Re-enabling PTY globally for orchestrated processes.
  • Changing command success/failure semantics.
  • Reformatting every tool's native output into a DevTools-specific format.
  • Adding decorative grouping to JSON or machine-readable output modes.

Acceptance Criteria

Functional Criteria

  • Nested commands keep colored output in an interactive console when the parent output is decorated and no NO_COLOR opt-out is present.
  • Nested commands can preserve colored output in GitHub Actions when FORCE_COLOR=1 is configured.
  • The fix does not reintroduce PTY usage for normal orchestrated processes.
  • Local aggregate command output has clear command boundaries around nested process output.
  • JSON and pretty-JSON command modes remain machine-readable and do not include local section decoration.
  • stdout and stderr routing remain correct for blocking and detached processes.

Regression Criteria

  • Process queue tests cover color environment propagation without using real process instances except the class under test where unavoidable.
  • Process queue tests cover visible local section boundaries for blocking process output.
  • Existing PTY regression coverage remains in place and confirms setPty() is not called for queued processes.
  • At least one aggregate command is manually verified in a local terminal and in a GitHub Actions-style environment with FORCE_COLOR=1.

Suggested Verification Commands

  • composer dev-tools standards
  • FORCE_COLOR=1 composer dev-tools standards
  • composer dev-tools --json
  • composer dev-tools tests -- --filter=ProcessQueueTest
  • composer dev-tools code-style -- --fix --json

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingenhancementNew feature or request

    Type

    Projects

    Status

    Released

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions