vxcontrol/pentagi · warning
listing container directory '%s': %w
Error message
listing container directory '%s': %w
What it means
After stat'ing every entry, ListContainerDir checks ctx.Err() and fails the whole listing if the context was cancelled, treating cancellation as a directory-level fault (the client is gone) rather than returning a partial listing. The wrapped cause is context.Canceled or context.DeadlineExceeded.
Source
Thrown at backend/pkg/docker/client.go:1025
if err != nil {
return ContainerDirListing{}, fmt.Errorf("failed to inspect list exec for '%s': %w", dirPath, err)
}
if inspect.ExitCode != 0 {
return ContainerDirListing{}, fmt.Errorf("list command failed for '%s' with exit code %d: %s", dirPath, inspect.ExitCode, string(output))
}
entryPaths, truncated := parseFindEntries(output)
stats, failures := statContainerEntries(ctx, entryPaths, containerListWorkers, func(ctx context.Context, entryPath string) (container.PathStat, error) {
return dc.ContainerStatPath(ctx, containerID, entryPath)
})
// A cancelled context is a directory-level fault (the client is gone), not a
// partial listing, so surface it as an error. Entries that individually failed
// to stat are carried in Failures — the find exec already proved the container
// alive, so a live directory degrades rather than 500s even if every entry failed.
if err := ctx.Err(); err != nil {
return ContainerDirListing{}, fmt.Errorf("listing container directory '%s': %w", dirPath, err)
}
listing := ContainerDirListing{Files: stats, Truncated: truncated}
for _, f := range failures {
listing.Failures = append(listing.Failures, ContainerEntryError{
Name: path.Base(f.name),
Path: f.name,
Err: f.err,
})
}
return listing, nil
}
// parseFindEntries splits `find -print0` output (NUL-delimited absolute paths),
// dropping empties, and caps the result at maxListEntries — returning truncated=true
// so the caller reports the partiality instead of failing or silently dropping.
func parseFindEntries(output []byte) (entries []string, truncated bool) {View on GitHub (pinned to ea665308ba)
Solutions
- Increase the context deadline passed to ListContainerDir for large directories
- List a narrower directory to reduce per-entry stat work
- Ensure the caller does not cancel the context before results are consumed
- Retry with a fresh, longer-lived context if the cancellation was transient
Example fix
// before: short timeout expires mid-listing ctx, cancel := context.WithTimeout(ctx, 2*time.Second) // after: size the deadline to the directory ctx, cancel := context.WithTimeout(ctx, 30*time.Second) listing, err := dc.ListContainerDir(ctx, id, dir)
Defensive patterns
Strategy: try-catch
Try / catch
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
listing, err := client.ListContainerDir(ctx, containerID, dir)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
return retryListingWithLargerDeadline(containerID, dir)
}
return err
} Prevention
- Size context deadlines to directory size and daemon latency
- Don't cancel parent contexts while listings are in flight
- For huge dirs, list incrementally by subdirectory
- Propagate request cancellation only after results are no longer needed
When it happens
Trigger: The caller's context is cancelled or its deadline expires while ListContainerDir is running (per-entry ContainerStatPath calls against a large directory), or the HTTP request that triggered the listing is aborted by the client.
Common situations: Listing huge directories against a slow/remote daemon exceeding deadlines; aborted HTTP requests; service shutdown draining in-flight work.
Related errors
- wait cancelled — the assistant session was interrupted while
- flow %d input processing timeout: %w
- stop timed out after %s — the task may still be winding down
- task stop timeout
- failed to acquire advisory lock %q: %w
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/845e4320404b6a6b.
Report an issue: GitHub.