{"record":{"id":"3cce740ddb5e4867","repo":"docker/compose","slug":"inspecting-q-w","errorCode":null,"errorMessage":"inspecting %q: %w","messagePattern":"inspecting %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/sync/tar.go","lineNumber":157,"sourceCode":"}\n\n// ArchivePathsIfExist creates a tar archive of all local files in `paths`. It quietly skips any paths that don't exist.\nfunc (a *ArchiveBuilder) ArchivePathsIfExist(paths []PathMapping) error {\n\t// In order to handle overlapping syncs, we\n\t// 1) collect all the entries,\n\t// 2) de-dupe them, with last-one-wins semantics\n\t// 3) write all the entries\n\t//\n\t// It's not obvious that this is the correct behavior. A better approach\n\t// (that's more in-line with how syncs work) might ignore files in earlier\n\t// path mappings when we know they're going to be \"synced\" over.\n\t// There's a bunch of subtle product decisions about how overlapping path\n\t// mappings work that we're not sure about.\n\tvar entries []archiveEntry\n\tfor _, p := range paths {\n\t\tnewEntries, err := a.entriesForPath(p.HostPath, p.ContainerPath)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"inspecting %q: %w\", p.HostPath, err)\n\t\t}\n\n\t\tentries = append(entries, newEntries...)\n\t}\n\n\tentries = dedupeEntries(entries)\n\tfor _, entry := range entries {\n\t\terr := a.writeEntry(entry)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"archiving %q: %w\", entry.path, err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *ArchiveBuilder) writeEntry(entry archiveEntry) error {\n\tpathInTar := entry.path\n\theader := entry.header","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/docker/compose/blob/ddc4b044b62e9f715212ea4143fa830fac76382f/internal/sync/tar.go#L139-L175","documentation":"ArchiveBuilder.ArchivePathsIfExist walks each HostPath via entriesForPath to collect tar entries. Walking errors that are NOT 'file does not exist' (which is silently skipped there) bubble up wrapped as 'inspecting %q'. It means compose could not read the local tree for the named path.","triggerScenarios":"os.Stat(localPath) on the mapping root returning a non-NotExist error, or os.Readlink failing on a symlink found during the walk (e.g. permission denied on the link target lookup).","commonSituations":"Bind-sync roots with mixed ownership (root-owned dirs under a user-run compose), symlinks on restricted paths, or filesystems returning EACCES/EIO during recursive enumeration.","solutions":["Fix read/traverse permissions on the named host path and its parents for the user running compose","Re-check broken symlinks under the synced root: find <path> -xtype l; repair or exclude them","Exclude unreadable subtrees from the sync mapping (paths are explicit in PathMapping)","If it is a network mount, remount and retry once it is healthy"],"exampleFix":"# reproduce the walk failure as the same user\nfind '/path/from/error' -newer /dev/null 2>&1 | head\n# permission denied -> grant read+execute:\nchmod -R u+rX /path/from/error","handlingStrategy":"validation","validationCode":"// dry-run readability of the sync root before Sync\nif err := checkReadableTree(hostRoot); err != nil { // filepath.Walk with os.Open on each dir\n    return err\n}","typeGuard":null,"tryCatchPattern":"// match on the 'inspecting %q' wrapped PathError to pinpoint the unreadable subtree, grant perms, retry Sync","preventionTips":["Run find <root> -type d ! -readable as the compose user before enabling watch","Exclude vendor/CI-created root-owned dirs from sync mappings"],"tags":["filesystem","permissions","sync","tar","go"],"backgroundTag":null,"analyzedSha":"ddc4b044b62e9f715212ea4143fa830fac76382f","analyzedAt":"2026-08-15T13:31:42.319Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}