{"record":{"id":"bf64d8e6d61cbaea","repo":"pocketbase/pocketbase","slug":"failed-to-resolve-hooksdir-symlink-w","errorCode":null,"errorMessage":"failed to resolve hooksDir symlink: %w","messagePattern":"failed to resolve hooksDir symlink: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"plugins/jsvm/jsvm.go","lineNumber":384,"sourceCode":"// watchHooks initializes a hooks file watcher that will restart the\n// application (*if possible) in case of a change in the hooks directory.\n//\n// This method does nothing if the hooks directory is missing.\nfunc (p *plugin) watchHooks() error {\n\twatchDir := p.config.HooksDir\n\n\thooksDirInfo, err := os.Lstat(p.config.HooksDir)\n\tif err != nil {\n\t\tif errors.Is(err, fs.ErrNotExist) {\n\t\t\treturn nil // no hooks dir to watch\n\t\t}\n\t\treturn err\n\t}\n\n\tif hooksDirInfo.Mode()&os.ModeSymlink == os.ModeSymlink {\n\t\twatchDir, err = filepath.EvalSymlinks(p.config.HooksDir)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to resolve hooksDir symlink: %w\", err)\n\t\t}\n\t}\n\n\twatcher, err := fsnotify.NewWatcher()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar debounceTimer *time.Timer\n\n\tstopDebounceTimer := func() {\n\t\tif debounceTimer != nil {\n\t\t\tdebounceTimer.Stop()\n\t\t\tdebounceTimer = nil\n\t\t}\n\t}\n\n\tp.app.OnTerminate().BindFunc(func(e *core.TerminateEvent) error {","sourceCodeStart":366,"sourceCodeEnd":402,"githubUrl":"https://github.com/pocketbase/pocketbase/blob/5d217ddb50cb144d80a5d0b0bdf11b52b2c3e457/plugins/jsvm/jsvm.go#L366-L402","documentation":"Returned by jsvm's watchHooks() (plugins/jsvm/jsvm.go:384) when os.Lstat shows that the configured hooks directory is a symlink but filepath.EvalSymlinks fails to resolve it. The watcher needs the real path so fsnotify events match; if the symlink is broken or loops, resolution fails. The caller logs this only as a yellow warning ('Unable to init hooks watcher'), so it degrades dev-mode hot reload rather than crashing.","triggerScenarios":"pb_hooks is a symlink whose target was deleted (broken symlink); a symlink chain with a cycle; missing search permission on a path component of the target; on some network filesystems where symlink evaluation returns IO errors.","commonSituations":"Docker deployments that symlink pb_hooks to a mounted volume and later remove the volume; monorepos where pb_hooks symlinks into a shared packages dir that a teammate has not cloned; macOS/Windows hosts with case-mismatched target names.","solutions":["Resolve or remove the dangling symlink: `readlink pb_hooks` to see the target, then recreate it (`ln -sfn /real/path pb_hooks`) or replace it with a real directory.","Ensure the target path exists and the process user can traverse every component of it.","Avoid symlink cycles (a → b → a); keep a single-level symlink to a real directory.","If you do not need hot reload, leave HooksWatch disabled — watchHooks is not invoked and this error cannot occur."],"exampleFix":"# before: broken symlink\npb_hooks -> /mnt/shared/pb_hooks   # volume deleted\n\n# after: point at an existing dir (or use a real dir)\nrm pb_hooks\nln -sfn /srv/app/pb_hooks pb_hooks\nls -lL pb_hooks  # verify target resolves","handlingStrategy":"validation","validationCode":"// Before enabling HooksWatch, confirm the hooks dir resolves to a real directory.\nfunc resolvableHooksDir(dir string) (string, error) {\n    resolved, err := filepath.EvalSymlinks(dir)\n    if err != nil {\n        return \"\", fmt.Errorf(\"hooks dir does not resolve (broken symlink?): %w\", err)\n    }\n    info, err := os.Stat(resolved)\n    if err != nil || !info.IsDir() {\n        return \"\", fmt.Errorf(\"resolved path %q is not a directory\", resolved)\n    }\n    return resolved, nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Verify symlink targets with `ls -lL pb_hooks` after every environment change.","Prefer bind-mounting real directories in Docker over symlinking into volumes.","Avoid symlink chains and cycles; keep pb_hooks a single-hop link or a real dir.","Remember this failure only degrades hot reload (warning log) — it will silently cost you watch mode in dev."],"tags":["go","pocketbase","jsvm","hooks","filesystem","symlink","fsnotify"],"backgroundTag":null,"analyzedSha":"5d217ddb50cb144d80a5d0b0bdf11b52b2c3e457","analyzedAt":"2026-08-15T10:06:33.165Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}