{"record":{"id":"62def5c5d4b0b528","repo":"nektos/act","slug":"unable-to-close-file-v","errorCode":null,"errorMessage":"Unable to Close file: %v","messagePattern":"Unable to Close file: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/exprparser/functions.go","lineNumber":237,"sourceCode":"\n\tif len(files) == 0 {\n\t\treturn \"\", nil\n\t}\n\n\thasher := sha256.New()\n\n\tfor _, file := range files {\n\t\tf, err := os.Open(file)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"Unable to os.Open: %v\", err)\n\t\t}\n\n\t\tif _, err := io.Copy(hasher, f); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"Unable to io.Copy: %v\", err)\n\t\t}\n\n\t\tif err := f.Close(); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"Unable to Close file: %v\", err)\n\t\t}\n\t}\n\n\treturn hex.EncodeToString(hasher.Sum(nil)), nil\n}\n\nfunc (impl *interperterImpl) getNeedsTransitive(job *model.Job) []string {\n\tneeds := job.Needs()\n\n\tfor _, need := range needs {\n\t\tparentNeeds := impl.getNeedsTransitive(impl.config.Run.Workflow.GetJob(need))\n\t\tneeds = append(needs, parentNeeds...)\n\t}\n\n\treturn needs\n}\n\nfunc (impl *interperterImpl) always() (bool, error) {","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/nektos/act/blob/4f411281417e88660bea1c1a1749aa71ae0bd60f/pkg/exprparser/functions.go#L219-L255","documentation":"Thrown by act's expression interpreter when actionlint's expression parser fails to parse the text inside ${{ }}. The message embeds actionlint's own parse error, which names the offending token and column. This is a syntax-level failure in the GitHub Actions expression language.","triggerScenarios":"Typos like ${{ env. }} or ${{ if(x) }}; unbalanced parentheses/quotes; using shell syntax inside expressions ($(), backticks); missing '}}' terminator (act appends it, so inner stray '}}' breaks too).","commonSituations":"Converting shell logic into expressions; complex nested ternaries; copy-pasting from docs with smart quotes; older act versions being stricter/looser than GitHub.","solutions":["Read the embedded parser message — it points to the exact token and column.","Simplify the expression or move logic into the run: script.","Lint the workflow with actionlint to catch it statically.","Compare against GitHub's expression syntax docs; no shell constructs allowed inside ${{ }}."],"exampleFix":"# before\n${{ env.GREETING && echo hi }}\n# after\n${{ env.GREETING != '' && 'hi' || 'bye' }}","handlingStrategy":"validation","validationCode":"func expressionParses(expr string) bool {\n  _, err := actionlint.NewExprParser().Parse(actionlint.NewExprLexer(expr))\n  return err == nil\n}","typeGuard":null,"tryCatchPattern":"if v, err := interp.Evaluate(raw, status); err != nil {\n  if strings.Contains(err.Error(), 'Failed to parse') {\n    // surface the actionlint message to the workflow author\n    return fmt.Errorf('bad expression %q: %w', raw, err)\n  }\n  return err\n}","preventionTips":["Run actionlint in CI for every workflow change","Keep shell logic out of ${{ }}","Break long expressions into named env vars step by step"],"tags":["expressions","parsing","actionlint","workflow-yaml","syntax"],"backgroundTag":null,"analyzedSha":"4f411281417e88660bea1c1a1749aa71ae0bd60f","analyzedAt":"2026-08-15T09:19:46.307Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}