{"record":{"id":"ba4e10b3786c0c2a","repo":"windmill-labs/windmill","slug":"cannot-get-content-of-directory","errorCode":null,"errorMessage":"Cannot get content of directory","messagePattern":"Cannot get content of directory","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/src/commands/sync/sync.ts","lineNumber":1813,"sourceCode":"                },\n              };\n\n              // Yield the module lock file if present\n              if (mod.lock) {\n                const baseName = relPath.replace(/\\.[^.]+$/, \"\");\n                yield {\n                  isDirectory: false,\n                  path: path.join(moduleFolderPath, baseName + \".lock\"),\n                  async *getChildren() {},\n                  async getContentText() {\n                    return mod.lock!;\n                  },\n                };\n              }\n            }\n          },\n          async getContentText() {\n            throw new Error(\"Cannot get content of directory\");\n          },\n        });\n      }\n    }\n    if (kind == \"resource\") {\n      const content = await f.async(\"text\");\n      let parsed;\n      try {\n        parsed = JSON.parse(content);\n      } catch (error) {\n        log.error(`Failed to parse resource file content at path: ${p}`);\n        throw error;\n      }\n      const resourceType = parsed[\"resource_type\"];\n      const formatExtension = resourceTypeToFormatExtension[resourceType];\n      const isFileset = resourceTypeToIsFileset[resourceType] ?? false;\n\n      if (","sourceCodeStart":1795,"sourceCodeEnd":1831,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/cli/src/commands/sync/sync.ts#L1795-L1831","documentation":"ZipFSElement builds a DynFS tree over a zip archive. Directory entries expose a getContentText() that always throws 'Cannot get content of directory', because a directory has no text content. Hitting it means caller code tried to read text from a node that is actually a directory.","triggerScenarios":"Calling getContentText() on a zip-tree element without checking isDirectory first — e.g. iterating sync entries and reading content from every node, or a misclassified zip entry (a folder-looking key treated as a file).","commonSituations":"Custom tooling walking the sync FS and blindly reading all entries; a zip produced with file entries whose names end in '/' and get classified as directories; scripts expecting a flat file layout but receiving a folder (e.g. a fileset or flow folder).","solutions":["Check `entry.isDirectory` before calling getContentText() and handle directories by recursing into getChildren() instead","If you expected a file, verify the path — you likely passed a folder path (e.g. a `.fileset/` or `__mod/` directory)","Re-inspect the zip/export: if a genuine file is represented as a directory, fix the export","Skip directory nodes in generic walkers"],"exampleFix":"// before\nconst text = await entry.getContentText();\n// after\nif (entry.isDirectory) {\n  for (const child of await entry.getChildren()) { /* recurse */ }\n} else {\n  const text = await entry.getContentText();\n}","handlingStrategy":"type-guard","validationCode":"if (entry.isDirectory) {\n  // handle children instead of reading text\n} else {\n  const text = await entry.getContentText();\n}","typeGuard":"function isFileEntry(e: { isDirectory: boolean }): e is { isDirectory: false } {\n  return !e.isDirectory;\n}","tryCatchPattern":"try {\n  const text = await node.getContentText();\n} catch (e) {\n  if (e.message === 'Cannot get content of directory') {\n    for (const child of await node.getChildren()) { /* recurse */ }\n  } else throw e;\n}","preventionTips":["Always check isDirectory before getContentText()","Walk trees via getChildren() and read only leaves","Verify zip entry names — trailing '/' entries are directories"],"tags":["zip","filesystem","sync","api-misuse"],"backgroundTag":"read-directory-as-file","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}