{"record":{"id":"9bcac5766a543fe4","repo":"ipfs/kubo","slug":"expected-mfs-directory-didn-t-get-it-this-is-li","errorCode":null,"errorMessage":"expected *mfs.Directory, didn't get it. This is likely a race condition","messagePattern":"expected \\*mfs\\.Directory, didn't get it\\. This is likely a race condition","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/commands/files.go","lineNumber":1604,"sourceCode":"\t\treturn \"\", fmt.Errorf(\"paths must start with a leading slash\")\n\t}\n\n\tcleaned := gopath.Clean(p)\n\tif p[len(p)-1] == '/' && p != \"/\" {\n\t\tcleaned += \"/\"\n\t}\n\treturn cleaned, nil\n}\n\nfunc getParentDir(root *mfs.Root, dir string) (*mfs.Directory, error) {\n\tparent, err := mfs.Lookup(root, dir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpdir, ok := parent.(*mfs.Directory)\n\tif !ok {\n\t\treturn nil, errors.New(\"expected *mfs.Directory, didn't get it. This is likely a race condition\")\n\t}\n\treturn pdir, nil\n}\n\nvar filesChmodCmd = &cmds.Command{\n\tStatus: cmds.Experimental,\n\tHelptext: cmds.HelpText{\n\t\tTagline: \"Change optional POSIX mode permissions\",\n\t\tShortDescription: `\nThe mode argument must be specified in Unix numeric notation.\n\n    $ ipfs files chmod 0644 /foo\n    $ ipfs files stat /foo\n    ...\n    Type: file\n    Mode: -rw-r--r-- (0644)\n    ...\n`,","sourceCodeStart":1586,"sourceCodeEnd":1622,"githubUrl":"https://github.com/ipfs/kubo/blob/329838acdfafae224582930457efe80aa217afc0/core/commands/files.go#L1586-L1622","documentation":"createCmdFetchParent (used by `files mkdir`, `files touch`, `files rm` and similar) looks up the parent of a target path and asserts it is *mfs.Directory. If the parent node is not a directory — or the node type changed concurrently between lookup and assertion — the assertion fails and this sentinel error is returned instead of panicking.","triggerScenarios":"Running a command whose parent path component is actually a file (e.g. `ipfs files mkdir /a/file/sub` where /a/file is a file); a concurrent `files rm`/`files mv` replacing the parent between lookup and assertion; a write/create command targeting a path whose intermediate entry was swapped.","commonSituations":"Scripts racing with each other on the same MFS tree; typos where a path component names a file; automation that renames directories while another job creates entries inside them.","solutions":["Check each intermediate path component with `ipfs files stat` and ensure parents are directories.","Re-run the command if the tree was being modified concurrently; serialize MFS mutations.","Create missing parent directories first (`ipfs files mkdir -p /a/b`) so the parent is guaranteed to be a directory."],"exampleFix":"// before: racy parent\nipfs files touch /data/${i}/file   # /data/${i} may be replaced mid-run\n// after: ensure parents exist and are directories first\nipfs files mkdir -p /data/${i}\nipfs files touch /data/${i}/file","handlingStrategy":"validation","validationCode":"for c in $(echo \"${P%/*}\" | tr '/' ' '); do [ \"$(ipfs files stat --format='<type>' \"/$c\")\" = \"directory\" ] || echo \"parent not a directory\"; done","typeGuard":"if d, ok := parent.(*mfs.Directory); ok { return d, nil }\nreturn nil, fmt.Errorf(\"parent of %s is not a directory\", path)","tryCatchPattern":"pdir, err := createCmdFetchParent(node, path)\nif err != nil {\n    if strings.Contains(err.Error(), \"expected *mfs.Directory\") { /* re-stat parents, retry once */ }\n    return err\n}","preventionTips":["Run `files mkdir -p` for the parent chain before creating children","Avoid concurrent rm/mv on ancestor directories of in-flight operations","Stat intermediate components when paths are user-supplied"],"tags":["mfs","race-condition","type-assertion","go"],"backgroundTag":"mfs-node-type-race","analyzedSha":"329838acdfafae224582930457efe80aa217afc0","analyzedAt":"2026-09-03T18:30:52.135Z","contentChangedAt":"2026-09-03T18:30:52.135Z","schemaVersion":2},"datasetVersion":"2026-09-11T00:17:11.886Z"}