Repository: nektos/act (language: go)

Issue:
`if` condition in composite action misbehaves with `uses`
### Bug report info

```plain text
act version:            0.2.65
GOOS:                   darwin
GOARCH:                 arm64
NumCPU:                 10
Docker host:            DOCKER_HOST environment variable is not set
Sockets found:
        $HOME/.docker/run/docker.sock
Config files:           
        /Users/.../Library/Application Support/act/actrc:
                -P ubuntu-latest=catthehacker/ubuntu:act-latest
                -P ubuntu-22.04=catthehacker/ubuntu:act-22.04
                -P ubuntu-20.04=catthehacker/ubuntu:act-20.04
                -P ubuntu-18.04=catthehacker/ubuntu:act-18.04
        .actrc:
                --container-architecture=linux/amd64
                --container-daemon-socket -
                -P arc-rs-dind=catthehacker/ubuntu:act-latest
Build info:
        Go version:            go1.22.5
        Module path:           command-line-arguments
        Main version:          
        Main path:             
        Main checksum:         
        Build settings:
                -buildmode:           exe
                -compiler:            gc
                -ldflags:             -X main.version=0.2.65
                DefaultGODEBUG:       httplaxcontentlength=1,httpmuxgo121=1,tls10server=1,tlsrsakex=1,tlsunsafeekm=1
                CGO_ENABLED:          1
                CGO_CFLAGS:           
                CGO_CPPFLAGS:         
                CGO_CXXFLAGS:         
                CGO_LDFLAGS:          
                GOARCH:               arm64
                GOOS:                 darwin

Error: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
```


### Command used with act

```sh
act pull_request -v \
  -e .act/event.json \
  -W .github/workflows/test-workflow.yml
```


### Describe issue

I'm passing a secret as an input to a composite action. In the composite action I want to run a third party action with `uses` based on whether the input is defined or not.

When I use an `if` condition on a shell step it works correctly, however on the step that `uses` the third party action the expression is suddenly evaluated to `false`.

### Link to GitHub repository

_No response_

### Workflow content

```yml
name: Test Workflow

on:
  pull_request:

jobs:
  test-job:
    runs-on: ubuntu-latest
    steps:
      - if: ${{ secrets.TEST_SECRET == '' }}
        run: echo "Secret undefined"

      - if: ${{ secrets.TEST_SECRET != '' }}
        run: echo "Secret defined"

      - uses: actions/checkout@v4

      - uses: ./.github/actions/composite-test
        with:
          test-input: ${{ secrets.TEST_SECRET }}


# Composite action

name: Composite test

inputs:
  test-input:
    description: 'My test input'
    required: false
    default: ''

runs:
  using: 'composite'
  steps:
    - name: 'Bash, input undefined'
      if: inputs.test-input == ''
      shell: bash
      run: echo "test-input defined"
    - name: 'Bash, input defined'
      if: inputs.test-input != ''
      shell: bash
      run: echo "test-input defined"
    - name: 'Action, input defined'
      if: inputs.test-input != ''
      uses: actions/checkout@v4
```


### Relevant log output

```sh
[Test Workflow/test-job] [DEBUG] evaluating expression '${{ ***s.TEST_SECRET == '' }}'
[Test Workflow/test-job] [DEBUG] expression '${{ ***s.TEST_SECRET == '' }}' evaluated to 'false'
[Test Workflow/test-job] [DEBUG] Skipping step 'echo "Secret undefined"' due to '${{ ***s.TEST_SECRET == '' }}'
...
[Test Workflow/test-job] [DEBUG] evaluating expression '${{ ***s.TEST_SECRET != '' }}'
[Test Workflow/test-job] [DEBUG] expression '${{ ***s.TEST_SECRET != '' }}' evaluated to 'true'
[Test Workflow/test-job] ⭐ Run Main echo "Secret defined"
...
[Test Workflow/test-job] [DEBUG] Wrote command 

echo "Secret defined"

 to 'workflow/1'
...
| Secret defined
[Test Workflow/test-job]   ✅  Success - Main echo "Secret defined"
...
[Test Workflow/test-job] ⭐ Run Main actions/checkout@v4
...
[Test Workflow/test-job]   ✅  Success - Main actions/checkout@v4
...
[Test Workflow/test-job] [DEBUG] expression '${{ ***s.TEST_SECRET }}' rewritten to 'format('{0}', ***s.TEST_SECRET)'
[Test Workflow/test-job] [DEBUG] evaluating expression 'format('{0}', ***s.TEST_SECRET)'
[Test Workflow/test-job] [DEBUG] expression 'format('{0}', ***s.TEST_SECRET)' evaluated to '%!t(string=***)'
...
[Test Workflow/test-job] ⭐ Run Main ./.github/actions/composite-test
...
[Test Workflow/test-job] [DEBUG] evaluating expression 'inputs.test-input == '''
[Test Workflow/test-job] [DEBUG] expression 'inputs.test-input == ''' evaluated to 'false'
[Test Workflow/test-job] [DEBUG] Skipping step 'Bash, input undefined' due to 'inputs.test-input == '''
...
[Test Workflow/test-job] [DEBUG] evaluating expression 'inputs.test-input != '''
[Test Workflow/test-job] [DEBUG] expression 'inputs.test-input != ''' evaluated to 'true'
[Test Workflow/test-job] ⭐ Run Main Bash, input defined
...
[Test Workflow/test-job] [DEBUG] Wrote command 

echo "test-input defined"

 to 'workflow/3-composite-1.sh'
...
| test-input defined
[Test Workflow/test-job]   ✅  Success - Main Bash, input defined
...
[Test Workflow/test-job] [DEBUG] evaluating expression 'inputs.test-input != '''
[Test Workflow/test-job] [DEBUG] expression 'inputs.test-input != ''' evaluated to 'false'
[Test Workflow/test-job] [DEBUG] Skipping step 'Action, input defined' due to 'inputs.test-input != '''
[Test Workflow/test-job] [DEBUG] Found revision: 7cc4b33435c8f25415bd4e1644374780f0c50e00
[Test Workflow/test-job]   ✅  Success - Main ./.github/actions/composite-test
...
```


### Additional information

My `event.json` only has base and head refs defined, nothing else.


Discussion / hints:
I have the same issue and the only condition that seems work is testing for null

`if: ! inputs.test-input`
I have a similar issue.

This works as expected, and prints out "Works! And this says 'true': true"

```yml
    - name: Shell command
      if: ${{ (fromJSON(inputs.myinput) == true }}
      shell: bash
      run: echo "Works! And this says 'true': ${{ fromJSON(inputs. inputs.myinput) }}"
```

However, this next one one errors out:

```yml
    - name: Run another action
      if: ${{ fromJSON(inputs.myinput) == true }}
      uses: myrepo/myaction@main
```

> Error in if-expression: "if: ${{ (fromJSON(inputs.myinput) == true }}" (Cannot parse non-string type invalid as JSON). 

My guess is that somehow the input is being forgotten, returning a null object or something that causes `fromJSON()` to choke on a non-existing value.


Regression of https://github.com/nektos/act/pull/2348?

Missing / unreliable tests of the code
I'm seeing the same behavior. My team makes heavy use of composite actions and being able to use this tool would have a significant impact for us.

> Regression of https://github.com/nektos/act/pull/2348?

@ChristopherHX Correct me if I'm wrong, but I believe the tests added in that PR won't detect an issue like this because the problem only surfaces when an `if` condition which uses inputs passed to the current composite action is being used, and the actions added in the testdata dir do not set such a condition.
I meant this bug might has been caused by #2348.

It doesn't read like we have the same impression what I meant.

So a first step to see if reverting that specifc commit works and run go build to get a binary, then verify if my assumption is correct.

Next steps would be to either propose a revert of #2348 and rerelease act or figure out to solve both the defect that PR aimed to fix and this one.

> My team makes heavy use of composite actions and being able to use this tool would have a significant impact for us.

_Downgrading act to a version (GitHub shows tags including the merged commit, just use the one lower) without that change is a temporary consumer solution_

I don't expect any resolution this month, my focus is on different projects.

This is on my backlog, my work for act cli is currently slow
Confirmed as Regression due to mergin #2348 

```yaml
name: "Test Composite Action"
description: "Test action uses composite"

inputs:
  b:
    default: true
  b2: {}

runs:
  using: "composite"
  steps:
  - uses: actions/github-script@v7
    if: inputs.b == 'true'
    with:
      script: |
        console.log(${{ tojson(inputs) }})
        if( ${{ tojson(inputs.b) }} != 'true' ) {
          process.exit(-1);
        }
      github-token: noop
  - uses: actions/github-script@v7
    if: inputs.b != 'true'
    with:
      script: |
        console.log(${{ tojson(inputs) }})
        if( ${{ tojson(inputs.b) }} == 'true' ) {
          process.exit(-1);
        }
      github-token: noop
  - uses: actions/github-script@v7
    if: inputs.b2 == 'false'
    with:
      script: |
        console.log(${{ tojson(inputs) }})
        if( ${{ tojson(inputs.b2) }} != 'false' ) {
          process.exit(-1);
        }
      github-token: noop
  - uses: actions/github-script@v7
    if: inputs.b2 != 'false'
    with:
      script: |
        console.log(${{ tojson(inputs) }})
        if( ${{ tojson(inputs.b2) }} == 'false' ) {
          process.exit(-1);
        }
      github-token: noop
```

called by
```yaml
name: uses-composite-check-for-input-in-if-uses
on: push

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./uses-composite-check-for-input-in-if-uses/composite_action
        with:
          b: true
          b2: true
      - uses: ./uses-composite-check-for-input-in-if-uses/composite_action
        with:
          b: false
          b2: false
      - uses: ./uses-composite-check-for-input-in-if-uses/composite_action
        with:
          b: true
          b2: false
      - uses: ./uses-composite-check-for-input-in-if-uses/composite_action
        with:
          b: false
          b2: true
```

I'm working on a fix right now, highest priority issue for act in my backlog.

Issues filed as defects of github-act-runner are processed and merged faster.
E.g. `git revert b5ad3c4acd89cc8a7d1ce8bdb41c16d48b2cdf36 && go build` on current master gives you are more recent act binary without this specfic defect

Fix patch:
diff --git a/pkg/runner/expression.go b/pkg/runner/expression.go
index cfd5d42f858..90643d09119 100644
--- a/pkg/runner/expression.go
+++ b/pkg/runner/expression.go
@@ -108,6 +108,19 @@ var hashfiles string
 
 // NewStepExpressionEvaluator creates a new evaluator
 func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step) ExpressionEvaluator {
+	return rc.NewStepExpressionEvaluatorExt(ctx, step, false)
+}
+
+// NewStepExpressionEvaluatorExt creates a new evaluator
+func (rc *RunContext) NewStepExpressionEvaluatorExt(ctx context.Context, step step, rcInputs bool) ExpressionEvaluator {
+	ghc := rc.getGithubContext(ctx)
+	if rcInputs {
+		return rc.newStepExpressionEvaluator(ctx, step, ghc, getEvaluatorInputs(ctx, rc, nil, ghc))
+	}
+	return rc.newStepExpressionEvaluator(ctx, step, ghc, getEvaluatorInputs(ctx, rc, step, ghc))
+}
+
+func (rc *RunContext) newStepExpressionEvaluator(ctx context.Context, step step, ghc *model.GithubContext, inputs map[string]interface{}) ExpressionEvaluator {
 	// todo: cleanup EvaluationEnvironment creation
 	job := rc.Run.Job()
 	strategy := make(map[string]interface{})
@@ -127,9 +140,6 @@ func (rc *RunContext) NewStepExpressionEvaluator(ctx context.Context, step step)
 		}
 	}
 
-	ghc := rc.getGithubContext(ctx)
-	inputs := getEvaluatorInputs(ctx, rc, step, ghc)
-
 	ee := &exprparser.EvaluationEnvironment{
 		Github:   step.getGithubContext(ctx),
 		Env:      *step.getEnv(),
diff --git a/pkg/runner/step.go b/pkg/runner/step.go
index e1bcf9ecfdc..7b761bac6f2 100644
--- a/pkg/runner/step.go
+++ b/pkg/runner/step.go
@@ -261,7 +261,7 @@ func isStepEnabled(ctx context.Context, expr string, step step, stage stepStage)
 		defaultStatusCheck = exprparser.DefaultStatusCheckSuccess
 	}
 
-	runStep, err := EvalBool(ctx, rc.NewStepExpressionEvaluator(ctx, step), expr, defaultStatusCheck)
+	runStep, err := EvalBool(ctx, rc.NewStepExpressionEvaluatorExt(ctx, step, stage == stepStageMain), expr, defaultStatusCheck)
 	if err != nil {
 		return false, fmt.Errorf("  \u274C  Error in if-expression: \"if: %s\" (%s)", expr, err)
 	}


Test patch:
diff --git a/pkg/runner/testdata/uses-composite-check-for-input-in-if-uses/composite_action/action.yml b/pkg/runner/testdata/uses-composite-check-for-input-in-if-uses/composite_action/action.yml
new file mode 100644
index 00000000000..f4f87412bc5
--- /dev/null
+++ b/pkg/runner/testdata/uses-composite-check-for-input-in-if-uses/composite_action/action.yml
@@ -0,0 +1,47 @@
+name: "Test Composite Action"
+description: "Test action uses composite"
+
+inputs:
+  b:
+    default: true
+  b2: {}
+
+runs:
+  using: "composite"
+  steps:
+  - uses: actions/github-script@v7
+    if: inputs.b == 'true'
+    with:
+      script: |
+        console.log(${{ tojson(inputs) }})
+        if( ${{ tojson(inputs.b) }} != 'true' ) {
+          process.exit(-1);
+        }
+      github-token: noop
+  - uses: actions/github-script@v7
+    if: inputs.b != 'true'
+    with:
+      script: |
+        console.log(${{ tojson(inputs) }})
+        if( ${{ tojson(inputs.b) }} == 'true' ) {
+          process.exit(-1);
+        }
+      github-token: noop
+  - uses: actions/github-script@v7
+    if: inputs.b2 == 'false'
+    with:
+      script: |
+        console.log(${{ tojson(inputs) }})
+        if( ${{ tojson(inputs.b2) }} != 'false' ) {
+          process.exit(-1);
+        }
+      github-token: noop
+  - uses: actions/github-script@v7
+    if: inputs.b2 != 'false'
+    with:
+      script: |
+        console.log(${{ tojson(inputs) }})
+        if( ${{ tojson(inputs.b2) }} == 'false' ) {
+          process.exit(-1);
+        }
+      github-token: noop
diff --git a/pkg/runner/testdata/uses-composite-check-for-input-in-if-uses/push.yml b/pkg/runner/testdata/uses-composite-check-for-input-in-if-uses/push.yml
new file mode 100644
index 00000000000..84441dcc893
--- /dev/null
+++ b/pkg/runner/testdata/uses-composite-check-for-input-in-if-uses/push.yml
@@ -0,0 +1,24 @@
+name: uses-composite-check-for-input-in-if-uses
+on: push
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: ./uses-composite-check-for-input-in-if-uses/composite_action
+        with:
+          b: true
+          b2: true
+      - uses: ./uses-composite-check-for-input-in-if-uses/composite_action
+        with:
+          b: false
+          b2: false
+      - uses: ./uses-composite-check-for-input-in-if-uses/composite_action
+        with:
+          b: true
+          b2: false
+      - uses: ./uses-composite-check-for-input-in-if-uses/composite_action
+        with:
+          b: false
+          b2: true
\ No newline at end of file