{"record":{"id":"edd66884ba8ed297","repo":"windmill-labs/windmill","slug":"cannot-get-content-of-folder","errorCode":null,"errorMessage":"Cannot get content of folder","messagePattern":"Cannot get content of folder","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/src/commands/sync/sync.ts","lineNumber":1901,"sourceCode":"      isDirectory: true,\n      path: p,\n      async *getChildren(): AsyncIterable<DynFSElement> {\n        for (const filename in zip.files) {\n          const file = zip.files[filename];\n          const totalPath = path.join(p, filename);\n          if (file.dir) {\n            const e = zip.folder(file.name)!;\n            yield _internal_folder(totalPath, e);\n          } else {\n            const fs = await _internal_file(totalPath, file);\n            for (const f of fs) {\n              yield f;\n            }\n          }\n        }\n      },\n      async getContentText(): Promise<string> {\n        throw new Error(\"Cannot get content of folder\");\n      },\n    };\n  }\n  return _internal_folder(\".\" + SEP, zip);\n}\n\n/**\n * Directories no walk over a workspace ever descends, whatever the sync scope:\n * dependency trees, and the dot-directories that hold tooling state and\n * fixtures. Exported because a second walk that disagrees with this one reads\n * files sync will never see, and draws conclusions from them.\n */\nexport function isNeverWalkedDir(dirName: string | undefined): boolean {\n  return (\n    dirName === \"node_modules\" || (dirName !== undefined && dirName.startsWith(\".\"))\n  );\n}\n","sourceCodeStart":1883,"sourceCodeEnd":1919,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/cli/src/commands/sync/sync.ts#L1883-L1919","documentation":"The internal folder node of the zip-based sync FS (the root '.' entry built by _internal_folder) also stubs getContentText() with 'Cannot get content of folder'. It is thrown when someone tries to read the text of the virtual root or any synthesized folder node rather than a real archived file.","triggerScenarios":"Calling getContentText() on the root element returned by ZipFSElement/_internal_folder, or on any synthesized folder while walking the zip tree; treating the whole zip tree as a single text blob.","commonSituations":"Scripts that dump every entry's content starting from the root; confusing the folder abstraction (getChildren) with file reading; debugging code that assumed the root had content.","solutions":["Start from getChildren() on the root and only call getContentText() on leaf file entries","Add an isDirectory check before any content read","If you need a text dump of the archive, iterate files individually rather than reading folders"],"exampleFix":"// before\nconst all = await root.getContentText();\n// after\nfor (const child of await root.getChildren()) {\n  if (!child.isDirectory) console.log(child.path, await child.getContentText());\n}","handlingStrategy":"try-catch","validationCode":"if (node.path === '.' + SEP || node.isDirectory) {\n  throw new Error('use getChildren() on folders');\n}\nconst text = await node.getContentText();","typeGuard":"function isRootFolder(e: DynFSElement): boolean {\n  return e.isDirectory && e.path === '.' + path.sep;\n}","tryCatchPattern":"try {\n  const text = await node.getContentText();\n} catch (e) {\n  if (e.message === 'Cannot get content of folder') {\n    // it's the root/virtual folder — iterate children instead\n  } else throw e;\n}","preventionTips":["Treat the zip root as a container only — always start with getChildren()","Never assume a single node holds the whole archive as text","Add isDirectory checks in any generic tree walker"],"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"}