Skip to content

GITHUB_OUTPUT variables work from workflow echo but not by core API call #1906

Open
@de-jcup

Description

@de-jcup

Description

When I write a key value pair by echo inside a workflow it can be used by other steps as variables and is visible inside the context. But when I use core.setOutput('key1', 'value1') it doesn't.

Also the content inside the $GITHUB_OUTPUT files is completely different.

Steps to reproduce

Set output with echo - works

Here an example which uses the described new way of setting outputs by environment variables. This works and the output file is in an expected way:

 - name: Print output by worfklow
      id: output_test_by_worfklow
      if: always()
      run: |
        echo "scan-trafficlight=RED" >> "$GITHUB_OUTPUT"  # Test Output 1
        echo "scan-findings-count=1" >> "$GITHUB_OUTPUT"  # Test Output 2

- name: Show all github output files
      id: output_file
      if: always()
      run: |
        echo "GITHUB_OUTPUT=$GITHUB_OUTPUT"
        DIRECTORY=$(dirname -- "$GITHUB_OUTPUT")
        # Check if the directory exists
        if [ ! -d "$DIRECTORY" ]; then
          echo "Directory does not exist."
          exit 1
        fi
        # Iterate through each file in the directory
        for FILE in "$DIRECTORY"/*; do
          # Check if the file name starts with "set_output"
          if [[ $(basename "$FILE") == set_output* ]]; then
            echo "----------------------------------------------------------------------------------------------------"
            echo "Contents of $FILE:"
            echo "----------------------------------------------------------------------------------------------------"
            cat "$FILE"
            echo # Add a newline for better readability
          fi
        done
- name: Print Context Information
      if: always()
      env:
        CONTEXT: ${{ toJson(steps) }}
      run: echo "$CONTEXT"

After execution I got following:

----------------------------------------------------------------------------------------------------
Contents of /runner/_work/_temp/_runner_file_commands/set_output_8c665a1b-cb6b-4962-96ca-3075398fe305:
----------------------------------------------------------------------------------------------------
scan-trafficlight=RED
scan-findings-count=1

and the (reduced) context output looks like this

"output_test_by_worfklow": {
    "outputs": {
      "scan-trafficlight": "RED",
      "scan-findings-count": "1"
    },
    "outcome": "success",
    "conclusion": "success"
  }

Means everything is working. Fine.

Set output via API - DOES NOT WORK

But when I write a custom action and use internally there :

core.setOutput('scan-trafficlight','RED')
core.setOutput('scan-findings-count','1')

to define output, it writes the files in a different way and the context does
not contain the two keys:

 - name: Print output by custom action
      id: output_test_by_custom_action
      uses: {orgname}/{customActionWhichSetsOutput}
- name: Show all github output files
      id: output_file
      if: always()
      run: |
        echo "GITHUB_OUTPUT=$GITHUB_OUTPUT"
        DIRECTORY=$(dirname -- "$GITHUB_OUTPUT")
        # Check if the directory exists
        if [ ! -d "$DIRECTORY" ]; then
          echo "Directory does not exist."
          exit 1
        fi
        # Iterate through each file in the directory
        for FILE in "$DIRECTORY"/*; do
          # Check if the file name starts with "set_output"
          if [[ $(basename "$FILE") == set_output* ]]; then
            echo "----------------------------------------------------------------------------------------------------"
            echo "Contents of $FILE:"
            echo "----------------------------------------------------------------------------------------------------"
            cat "$FILE"
            echo # Add a newline for better readability
          fi
        done
- name: Print Context Information
      if: always()
      env:
        CONTEXT: ${{ toJson(steps) }}
      run: echo "$CONTEXT"

After execution I got following:

----------------------------------------------------------------------------------------------------
Contents of /runner/_work/_temp/_runner_file_commands/set_output_9550026e-04b7-4a5e-963b-4d7838e73e4d:
----------------------------------------------------------------------------------------------------
scan-trafficlight<<ghadelimiter_12b579a9-fb20-4e41-8b6d-53dd97e57865
RED
ghadelimiter_12b579a9-fb20-4e41-8b6d-53dd97e57865
scan-findings-count<<ghadelimiter_b1042cbc-4b13-4648-851b-83535f96876c
1
ghadelimiter_b1042cbc-4b13-4648-851b-83535f96876c

and the (reduced) context output looks like this

"output_test_by_custom_action": {
    "outputs": {

    },
    "outcome": "success",
    "conclusion": "success"
  }

Means:

  1. The output is written different to the output by echo!
  2. The context does not contain output information/there is no access to it.

Additional info

Workaround (did not help)

I tried to write the file directly {key}={value}{os.EOL} but the 2 keys were still not
visible in context output.

Analyze
// https://github.com/actions/toolkit/blob/main/packages/core/src/core.ts#L192
export function setOutput(name: string, value: any): void {
  const filePath = process.env['GITHUB_OUTPUT'] || ''
  if (filePath) {
    return issueFileCommand('OUTPUT', prepareKeyValueMessage(name, value))
  }

  process.stdout.write(os.EOL)
  issueCommand('set-output', {name}, toCommandValue(value))
}

this calls

// https://github.com/actions/toolkit/blob/main/packages/core/src/file-command.ts
export function prepareKeyValueMessage(key: string, value: any): string {
  const delimiter = `ghadelimiter_${crypto.randomUUID()}`
  const convertedValue = toCommandValue(value)

  // These should realistically never happen, but just in case someone finds a
  // way to exploit uuid generation let's not allow keys or values that contain
  // the delimiter.
  if (key.includes(delimiter)) {
    throw new Error(
      `Unexpected input: name should not contain the delimiter "${delimiter}"`
    )
  }

  if (convertedValue.includes(delimiter)) {
    throw new Error(
      `Unexpected input: value should not contain the delimiter "${delimiter}"`
    )
  }

  return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}`
}

Hm... the usage of ${crypto.randomUUID()} looks odd: What shall here be the purpose of the crypto uuid? Was the intention to ensure keys are unqiue? It is always different. What was the real purpose for this. Sorry I did not get it.

Why does it not appear inside the context? Could it be that the read mechanism which sets the context is not able to read the new format?

Expected behavior

  1. I would expect that the output file structure would be same for echos and for API usages.
  2. using core.setOutput(...) should result in the output listed inside the context

Target

This appeared with core 1.10.1 and newer

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions