Three steps. First, add an id to the producing step. Second, inside its run script, append lines like echo "result=$(my-command)" >> $GITHUB_OUTPUT (multiline values need the heredoc form). Third, consume it later as ${{ steps.[id].outputs.result }}. Do not use the old ::set-output command; it is deprecated and disabled. Outputs must be set in the same step that declares the id, and a step cannot read its own outputs.

Context: Stack Overflow #59191913 (accepted answer, 145 votes): a workflow needs the output of the Run tests step in a later Slack notification step, and the asker does not know the mechanism. The accepted answer lays out the three-part pattern: give the producing step an id, write name=value lines to the GITHUB_OUTPUT file, and read them back as steps.[id].outputs.[name].