Skip to content

containerAttachStream's stderrEvent is emitted on wrong stream #96

Closed

Description

@knaerzche

I'm trying to capture demultiplexed stderr and stdout streams from a running container with following example code:

$container = await(
    $client->containerCreate(
        [
            'Image' => 'alpine',
            'Tty' => false,
            'Cmd' => [
                'sh',
                '-c',
                'i=0; while true; do echo "OUT $i"; echo "ERR $i" 1>&2; i=$((i+1)); sleep 1; done'
            ],
        ]
    )
);

$stream = $client
    ->containerAttachStream(
        $container['Id'],
        stream:true,
        stderrEvent: 'stderr',
    )
    ->on('data', function ($data) {
        echo 'stdout: ' . $data . "\n";
    })
    ->on('stderr', function ($data) {
        echo 'stderr: ' . $data . "\n";
    }
);


await(
    $client
        ->containerStart($container['Id']
    )
);

There's never anything logged for 'stderr' event and I'm not sure that ever worked.
The reason behind is that ReadableDemultiplexStream::containerAttachStream returns an UnwrapReadableStream:`

...
        // first inspect container to check TTY setting, then attach with appropriate log parser
        return \React\Promise\Stream\unwrapReadable($this->containerInspect($container)->then(function ($info) use ($url, $browser, $parser, $stderrEvent) {
            $stream = $parser->parsePlainStream($browser->requestStreaming('POST', $url));

            if (!$info['Config']['Tty']) {
                $stream = $parser->demultiplexStream($stream, $stderrEvent);
            }

            return $stream;
        }));
....

If I understand things correctly this is "only" a proxy stream, not the actual stream.
I understand you want to check Tty setting before using a ReadableDemultiplexStream. The problem is that the stream event listeners are subcribing to events of UnwrapReadableStream which in turn only emits (hardcoded) data, end, error and close events: https://github.com/reactphp/promise-stream/blob/f5fff26ec6ac4ea89168e49554a304bfedae1e7f/src/UnwrapReadableStream.php#L54 and there seems no way to add more.

One possible solution is to not use an promise, but await:

        // first inspect container to check TTY setting, then attach with appropriate log parser
        $info = await($this->containerInspect($container));
        $stream = $parser->parsePlainStream($browser->requestStreaming('POST', $url));

        if (!$info['Config']['Tty']) {
            $stream = $parser->demultiplexStream($stream, $stderrEvent);
        }

        return $stream;

But clue/reactphp-docker does currently not depend on react/async - so I'm not sure thats a correct fix for that issue.
I have no idea how to proper fix it or how to get the actual stream which emitts stderr events in my client code.

Activity

  1. reopened this on Oct 18, 2026
  2. clue commented on Oct 18, 2026

    @clue
    Owner

    @knaerzche Thanks for reporting and the detailed analysis! I can confirm this did not work as expected, and the same applies to containerLogsStream().

    Using await() would work, but it would require PHP 7.1+ and a dependency on react/async, which isn't really an option for the current 1.x line. Instead, #107 now uses its own stream in place of react/promise-stream (and accidentally closed this ticket a bit early), and #108 builds on top of this to forward the custom STDERR event as expected.

    This fix is part of a larger round of updates over the past week to get this project back into good shape, from #98 through #108, with more planned for v3 in #100. Your support really does make a difference here, so if you'd like to see more of this, consider becoming a sponsor ❤️

  3. added
    bugSomething isn't working
    on Oct 18, 2026
  4. added this to the v1.7.0 milestone on Oct 18, 2026
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

    bugSomething isn't working

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions