{"record":{"id":"cf3b8627ecd17721","repo":"nektos/act","slug":"non-string-path-passed-to-hashfiles","errorCode":null,"errorMessage":"Non-string path passed to hashFiles","messagePattern":"Non-string path passed to hashFiles","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/exprparser/functions.go","lineNumber":198,"sourceCode":"\treturn data, nil\n}\n\nfunc (impl *interperterImpl) hashFiles(paths ...reflect.Value) (string, error) {\n\tvar ps []gitignore.Pattern\n\n\tconst cwdPrefix = \".\" + string(filepath.Separator)\n\tconst excludeCwdPrefix = \"!\" + cwdPrefix\n\tfor _, path := range paths {\n\t\tif path.Kind() == reflect.String {\n\t\t\tcleanPath := path.String()\n\t\t\tif strings.HasPrefix(cleanPath, cwdPrefix) {\n\t\t\t\tcleanPath = cleanPath[len(cwdPrefix):]\n\t\t\t} else if strings.HasPrefix(cleanPath, excludeCwdPrefix) {\n\t\t\t\tcleanPath = \"!\" + cleanPath[len(excludeCwdPrefix):]\n\t\t\t}\n\t\t\tps = append(ps, gitignore.ParsePattern(cleanPath, nil))\n\t\t} else {\n\t\t\treturn \"\", fmt.Errorf(\"Non-string path passed to hashFiles\")\n\t\t}\n\t}\n\n\tmatcher := gitignore.NewMatcher(ps)\n\n\tvar files []string\n\tif err := filepath.Walk(impl.config.WorkingDir, func(path string, fi fs.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsansPrefix := strings.TrimPrefix(path, impl.config.WorkingDir+string(filepath.Separator))\n\t\tparts := strings.Split(sansPrefix, string(filepath.Separator))\n\t\tif fi.IsDir() || !matcher.Match(parts, fi.IsDir()) {\n\t\t\treturn nil\n\t\t}\n\t\tfiles = append(files, path)\n\t\treturn nil\n\t}); err != nil {","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/nektos/act/blob/4f411281417e88660bea1c1a1749aa71ae0bd60f/pkg/exprparser/functions.go#L180-L216","documentation":"Thrown by hashFiles() when filepath.Walk over the working directory returns an error (e.g. directory removed mid-walk, permission denied on a subdirectory, or the workspace path does not exist). The walk error is wrapped into this message.","triggerScenarios":"Running hashFiles() in a job whose workspace was not checked out (no actions/checkout); directories with unreadable permissions; concurrent cleanup deleting files during evaluation.","commonSituations":"hashFiles() in a job-level if: or cache key before checkout runs; act run against a directory with restrictive perms; container jobs with missing bind mounts.","solutions":["Add actions/checkout (or ensure the workspace exists) before evaluating hashFiles().","Check the wrapped error for permission issues and fix filesystem permissions.","Avoid hashFiles() in job-level expressions that run before any step."],"exampleFix":"# before\njobs:\n  build:\n    if: hashFiles('src/**') != ''\n    steps: [run: echo hi]\n# after\njobs:\n  build:\n    steps:\n      - uses: actions/checkout@v4\n      - run: echo ${{ hashFiles('src/**') }}","handlingStrategy":"try-catch","validationCode":"func workspaceWalkable(dir string) bool {\n  fi, err := os.Stat(dir)\n  return err == nil && fi.IsDir()\n}","typeGuard":null,"tryCatchPattern":"if h, err := hashFiles('src/**'); err != nil {\n  if strings.Contains(err.Error(), 'filepath.Walk') {\n    h = '' // no workspace yet: skip caching rather than fail\n  } else { return err }\n}","preventionTips":["Run actions/checkout before hashFiles","Do not use hashFiles in job-level if: conditions","Verify the working directory exists and is readable"],"tags":["expressions","hash-files","filesystem","checkout"],"backgroundTag":null,"analyzedSha":"4f411281417e88660bea1c1a1749aa71ae0bd60f","analyzedAt":"2026-08-15T09:19:46.307Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}