windmill-labs/windmill · error

Cannot get content of directory

Error message

Cannot get content of directory

What it means

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.

Source

Thrown at cli/src/commands/sync/sync.ts:1813

                },
              };

              // Yield the module lock file if present
              if (mod.lock) {
                const baseName = relPath.replace(/\.[^.]+$/, "");
                yield {
                  isDirectory: false,
                  path: path.join(moduleFolderPath, baseName + ".lock"),
                  async *getChildren() {},
                  async getContentText() {
                    return mod.lock!;
                  },
                };
              }
            }
          },
          async getContentText() {
            throw new Error("Cannot get content of directory");
          },
        });
      }
    }
    if (kind == "resource") {
      const content = await f.async("text");
      let parsed;
      try {
        parsed = JSON.parse(content);
      } catch (error) {
        log.error(`Failed to parse resource file content at path: ${p}`);
        throw error;
      }
      const resourceType = parsed["resource_type"];
      const formatExtension = resourceTypeToFormatExtension[resourceType];
      const isFileset = resourceTypeToIsFileset[resourceType] ?? false;

      if (

View on GitHub (pinned to e474e8803c)

Solutions

  1. Check `entry.isDirectory` before calling getContentText() and handle directories by recursing into getChildren() instead
  2. If you expected a file, verify the path — you likely passed a folder path (e.g. a `.fileset/` or `__mod/` directory)
  3. Re-inspect the zip/export: if a genuine file is represented as a directory, fix the export
  4. Skip directory nodes in generic walkers

Example fix

// before
const text = await entry.getContentText();
// after
if (entry.isDirectory) {
  for (const child of await entry.getChildren()) { /* recurse */ }
} else {
  const text = await entry.getContentText();
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (entry.isDirectory) {
  // handle children instead of reading text
} else {
  const text = await entry.getContentText();
}

Type guard

function isFileEntry(e: { isDirectory: boolean }): e is { isDirectory: false } {
  return !e.isDirectory;
}

Try / catch

try {
  const text = await node.getContentText();
} catch (e) {
  if (e.message === 'Cannot get content of directory') {
    for (const child of await node.getChildren()) { /* recurse */ }
  } else throw e;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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).

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/ba4e10b3786c0c2a. Report an issue: GitHub.