{"record":{"id":"1a657e3738609e00","repo":"louislam/dockge","slug":"compose-env-must-be-a-string","errorCode":null,"errorMessage":"Compose ENV must be a string","messagePattern":"Compose ENV must be a string","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"backend/agent-socket-handlers/docker-socket-handler.ts","lineNumber":345,"sourceCode":"                    ok: true,\n                    dockerNetworkList,\n                }, callback);\n            } catch (e) {\n                callbackError(e, callback);\n            }\n        });\n    }\n\n    async saveStack(server : DockgeServer, name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown) : Promise<Stack> {\n        // Check types\n        if (typeof(name) !== \"string\") {\n            throw new ValidationError(\"Name must be a string\");\n        }\n        if (typeof(composeYAML) !== \"string\") {\n            throw new ValidationError(\"Compose YAML must be a string\");\n        }\n        if (typeof(composeENV) !== \"string\") {\n            throw new ValidationError(\"Compose ENV must be a string\");\n        }\n        if (typeof(isAdd) !== \"boolean\") {\n            throw new ValidationError(\"isAdd must be a boolean\");\n        }\n\n        const stack = new Stack(server, name, composeYAML, composeENV, false);\n        await stack.save(isAdd);\n        return stack;\n    }\n\n}\n\n","sourceCodeStart":327,"sourceCodeEnd":358,"githubUrl":"https://github.com/louislam/dockge/blob/f809ae192b571944ad773e9866d3e67064ae8043/backend/agent-socket-handlers/docker-socket-handler.ts#L327-L358","documentation":"ValidationError from saveStack: the composeENV argument (contents of the stack's .env file) must be a string. It is written to disk as-is, so undefined/null/objects are rejected before Stack creation and save.","triggerScenarios":"Emitting 'create'/'stack' with composeENV undefined (env editor never initialized), null after a failed env fetch, or an object (key/value map sent instead of dotenv-format text).","commonSituations":"New stacks where the env pane has no value yet; passing a parsed env object like { KEY: 'value' } instead of 'KEY=value' lines; frontend state reset losing the env string.","solutions":["Send env as dotenv-style text ('KEY=value' lines), defaulting to '' when empty","Join a key/value map before sending: Object.entries(map).map(([k,v]) => `${k}=${v}`).join('\\n')","Initialize the env editor with an empty string, never undefined","Validate typeof env === 'string' before emit"],"exampleFix":"// before\nsocket.emit(\"create\", name, yaml, envMap, isAdd, cb); // object\n// after\nconst envStr = Object.entries(envMap).map(([k, v]) => `${k}=${v}`).join(\"\\n\");\nsocket.emit(\"create\", name, yaml, envStr, isAdd, cb);","handlingStrategy":"validation","validationCode":"function toEnvString(env: unknown): string {\n  if (typeof env === \"string\") return env;\n  if (env && typeof env === \"object\") {\n    return Object.entries(env).map(([k, v]) => `${k}=${v}`).join(\"\\n\");\n  }\n  return \"\"; // undefined/null -> empty .env\n}","typeGuard":"const isEnvString = (v: unknown): v is string => typeof v === \"string\";","tryCatchPattern":"try {\n  const envStr = toEnvString(rawEnv);\n  socket.emit(\"create\", name, yaml, envStr, isAdd, cb);\n} catch (e) { showError((e as Error).message); }","preventionTips":["Represent .env as dotenv-format text end to end","Default the env editor to an empty string on mount","Convert key/value maps to 'KEY=value' lines before emitting"],"tags":["validation","socket-io","dotenv","type-error"],"backgroundTag":"invalid-argument-type","analyzedSha":"f809ae192b571944ad773e9866d3e67064ae8043","analyzedAt":"2026-08-31T19:13:50.919Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}