Your indexer's filepath.WalkDir stops at one unreadable directory and writes a partial manifest — how should the callback handle that error?
answer
- the third parameter asks, not reports
- the walk already gave up there
- returning it ends everything
- one path, two callback calls
- a smaller manifest still looks valid
basics
~20 sfilepath.WalkDir calls the callback a second time for that directory with err set to the read failure. Returning that error stops the whole walk; record it and return nil to keep walking, then refuse to publish the manifest as complete.
solid answer
~50 sThe third parameter of an `fs.WalkDirFunc` is how the walk reports a directory it could not read, and the callback decides the policy. When a directory's read fails, `filepath.WalkDir` invokes the callback twice for that path: once beforehand with `err == nil`, giving you a chance to prune it, and once afterwards with `err` set. The reflexive `if err != nil { return err }` turns one unreadable directory into an aborted traversal, which is how you get a manifest that looks fine and is silently missing half the tree. The production shape is to classify: for something like `fs.ErrPermission` or a file that vanished mid-walk, count it, log the path, and return `nil` so the rest of the tree is still walked; for anything you cannot tolerate, return the error and fail loudly. Then let the counter decide whether the manifest is publishable — a partial index that quietly replaces a good one is worse than no index.
code
go · 12 linesvar skipped []string
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
if errors.Is(err, fs.ErrPermission) || errors.Is(err, fs.ErrNotExist) {
skipped = append(skipped, path)
return nil // keep walking the rest of the tree
}
return fmt.Errorf("walking %s: %w", path, err)
}
// d is only safe to use once err is known to be nil
return manifest.Add(path, d)
})go deeper
Recall that a walk callback takes three parameters and that the third one is an error the walk hands you. Returning it stops the traversal; returning nil lets it carry on.
Explain the two cases that produce a non-nil error, that the entry parameter is nil when the root stat fails, and that a failing directory triggers two calls for the same path.
Demonstrate the production instinct: classify with errors.Is, tolerate a named class such as permission denied or a vanished entry, count what you skipped, and never let a partial result replace a good one unnoticed.
Own the contract of the artefact. Decide what completeness the manifest promises, what skip rate fails the job, and who is paged when it drifts — those choices matter more than the callback body.
## The error parameter is a policy hook ```go type fs.WalkDirFunc func(path string, d fs.DirEntry, err error) error ``` That third parameter is not "an error the walk had"; it is the walk *asking you what to do*. The traversal has already decided it cannot go into that directory. Your return value decides whether that is fatal for the whole job. `filepath.WalkDir` passes a non-nil `err` in exactly two situations. **The root could not be stat'ed.** Before walking anything, the traversal stats `root`. If that fails, the callback is invoked once with `path` set to the root, `err` set to the failure, and **`d` set to nil**. That nil is a real production hazard: a callback whose first line is `if d.IsDir()` panics on a mistyped root path. Check `err` before you touch `d`. **A directory could not be read.** Here the callback is called **twice** for the same path. The first call happens before the read is attempted, with `err == nil` and a valid `d` — this is your chance to return `fs.SkipDir` and avoid the read entirely. If the read then fails, the callback is called again with the same path and `err` set to the failure. (If the read succeeds there is no second call.) Callbacks that count entries or append to a manifest keyed by path need to be aware of that second visit, or a rare double-count appears only for directories that fail. Note what is *not* on this list: a permission error on an individual file. The walk never opens files, so a file you cannot read is reported perfectly normally; the failure surfaces later, in your own code, when you try to open it. ## The three policies **Return the error.** The traversal stops immediately and `filepath.WalkDir` returns that error. Correct when any gap invalidates the result — a backup or a checksum over the whole tree. **Return nil.** The walk continues with the next sibling; the unreadable directory's contents are simply absent. Correct when the tree is expected to be imperfect, but only if you *record* the fact somewhere. **Return `fs.SkipDir`.** Equivalent to nil in the failure case, since the walk was not going to descend anyway. It reads more clearly on the first, `err == nil` call when you are pruning deliberately. ## Classify, do not blanket-tolerate Blanket `return nil` is the mirror-image defect of blanket `return err`: it converts every failure, including a broken mount or a root that does not exist, into a quietly smaller output. Classify with `errors.Is`: ```go if err != nil { switch { case errors.Is(err, fs.ErrPermission): skipped = append(skipped, path) return nil case errors.Is(err, fs.ErrNotExist): // vanished between the parent's read and this visit; normal on a live tree return nil default: return fmt.Errorf("walking %s: %w", path, err) } } ``` `fs.ErrNotExist` deserves special mention. A directory listing is a snapshot; on a tree another process is writing to, entries disappear between the parent's read and the visit. Treating that as fatal makes an indexer that fails randomly under load, and it is exactly the kind of bug that never reproduces on a developer's quiet checkout. ## The output is the real question The interesting part of this scenario is not the callback — it is what happens downstream. A walk that tolerated errors produced a manifest that is *structurally valid and semantically incomplete*, and nothing about its shape says so. Whatever consumes it will treat missing assets as deleted assets. So the tolerant callback comes with an obligation: * Keep a count and a sample of skipped paths, and put them in the manifest itself or in the job's result, not only in a log line. * Decide a threshold. Zero skips: publish. A handful of known-unreadable directories: publish with the skip list recorded. Anything beyond that: fail the job rather than replace a good manifest with a lossy one. * Emit the skipped count as a metric, because the interesting failure is a slow drift upward, not a single bad run. "Fail closed on a partial result that replaces a known-good one" is the judgment being tested here. The API question — which parameter, called how many times — is the entry ticket. ## The shape to write ```go var skipped int err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { if err != nil { if errors.Is(err, fs.ErrPermission) { skipped++ return nil } return err } // ... record the entry return nil }) ``` Every `err != nil` branch is checked before `d` is touched, tolerance is narrow and named, and the count leaves the closure so the caller can refuse to publish.
- When can the fs.DirEntry parameter be nil inside a filepath.WalkDir callback?When the initial stat of the root path fails. The callback is then invoked once with the root path, a nil entry and the error. Any callback that reaches for d.IsDir() or d.Name() before checking err panics on a mistyped or missing root, which is why the error check must come first in the function body.
- Why is the callback invoked twice for a directory that cannot be read?The first call happens before the read is attempted, with a nil error, so you can prune the directory with fs.SkipDir and avoid the read altogether. The second call reports the read failure. If the read succeeds there is no second call. Callbacks that count entries or append per path must tolerate that repeat for failing directories.
- What is wrong with returning nil for every error the walk reports?It converts every failure, including a nonexistent root or a broken mount, into a silently smaller result. The output stays structurally valid, so downstream consumers read missing entries as deleted ones. Tolerate a named class such as fs.ErrPermission, count what you skipped, and let the caller refuse to publish a result that skipped too much.
- Is a file you lack permission to read reported through this error parameter?No. The walk only reads directories, so an unreadable regular file is reported to the callback like any other entry with a nil error. The permission failure appears later, in your own code, when you try to open it. Only an unreadable directory, or a failed stat of the root, arrives through the callback's error parameter.
saying these in an interview costs you the question
- Reflexively returns the error, aborting the entire walk
- Returns nil for every error and publishes the short result anyway
- Touches d before checking the error parameter
- Assumes an unreadable file is reported through the callback's error
- Thinks the walk collects errors and continues by default