{"record":{"id":"a0a0dcb84e473141","repo":"windmill-labs/windmill","slug":"content-of-raw-app-file-filepath-is-not-a-strin","errorCode":null,"errorMessage":"Content of raw app file ${filePath} is not a string","messagePattern":"Content of raw app file (.+?) is not a string","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/src/commands/sync/sync.ts","lineNumber":1470,"sourceCode":"                  filePath === \"/DATATABLES.md\"\n                ) {\n                  continue;\n                }\n                // Strip only a leading `/` (keys are app-root-relative), so the\n                // relative path handed to the guard matches what the backend's\n                // `strip_prefix('/')` validates — the two must not disagree on a\n                // non-`/` key, or a deploy the backend allows would abort the pull.\n                const filePathInApp = rawAppPathWithinFolder(\n                  finalPath,\n                  filePath.replace(/^\\//, \"\"),\n                );\n                yield {\n                  isDirectory: false,\n                  path: filePathInApp,\n                  async *getChildren() {},\n                  async getContentText() {\n                    if (typeof content !== \"string\") {\n                      throw new Error(\n                        `Content of raw app file ${filePath} is not a string`,\n                      );\n                    }\n                    return content as string;\n                  },\n                };\n              }\n            } catch (error) {\n              log.error(`Failed to extract files for raw app at path: ${p}`);\n              throw error;\n            }\n\n            // Yield inline script content and lock files\n            for (const s of inlineScripts) {\n              yield {\n                isDirectory: false,\n                path: path.join(finalPath, APP_BACKEND_FOLDER, s.path),\n                async *getChildren() {},","sourceCodeStart":1452,"sourceCodeEnd":1488,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/cli/src/commands/sync/sync.ts#L1452-L1488","documentation":"When reading a raw app's files from a zip archive during sync, each entry lazily returns its text via getContentText(). The CLI throws this error if the stored content value for a file is not a string, meaning the raw app's `value.files` (or equivalent zip data) holds a non-string (object, number, null) where file text was expected.","triggerScenarios":"Pushing/pulling a raw app whose `value.files` map contains a non-string value for some file key — e.g. someone pasted an object instead of file text, a tool serialized content as {content: ...}, or an older/newer export format is being read by an incompatible CLI version.","commonSituations":"Hand-editing or script-generating a raw app JSON with wrong shapes; version mismatch between the instance that exported the app and the CLI reading it; a migration converting binary/encoded file content into objects.","solutions":["Open the raw app definition and make every `value.files` entry a plain string of file text (encode binaries as needed, e.g. base64 with the convention your app uses)","Check CLI/instance version compatibility and upgrade the CLI to match the export format","Re-export the raw app from the instance rather than hand-editing the JSON","If calling this API from code, validate `typeof content === 'string'` before constructing the entry"],"exampleFix":"// before\n\"files\": { \"index.js\": { \"content\": \"console.log(1)\" } }\n// after\n\"files\": { \"index.js\": \"console.log(1)\" }","handlingStrategy":"type-guard","validationCode":"const files = rawApp.value.files ?? {};\nconst bad = Object.entries(files).filter(([, v]) => typeof v !== 'string');\nif (bad.length) throw new Error(`non-string file content: ${bad.map(([k]) => k).join(', ')}`);","typeGuard":"function isStringFileMap(v: unknown): v is Record<string, string> {\n  return typeof v === 'object' && v !== null &&\n    Object.values(v).every(x => typeof x === 'string');\n}","tryCatchPattern":"try {\n  const text = await entry.getContentText();\n} catch (e) {\n  if (String(e).includes('is not a string')) {\n    // inspect the app definition's value.files entry for this path\n  } else throw e;\n}","preventionTips":["Keep value.files values as plain strings of file text","Re-export apps from the instance instead of hand-editing exported JSON","Match CLI version to the instance version before syncing raw apps"],"tags":["raw-app","zip","type-error","sync"],"backgroundTag":"invalid-content-type","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"}