{"record":{"id":"e067b4161bf51903","repo":"louislam/dockge","slug":"compose-yaml-must-be-a-string","errorCode":null,"errorMessage":"Compose YAML must be a string","messagePattern":"Compose YAML must be a string","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"backend/agent-socket-handlers/docker-socket-handler.ts","lineNumber":342,"sourceCode":"                checkLogin(socket);\n                const dockerNetworkList = await server.getDockerNetworkList();\n                callbackResult({\n                    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":324,"sourceCodeEnd":358,"githubUrl":"https://github.com/louislam/dockge/blob/f809ae192b571944ad773e9866d3e67064ae8043/backend/agent-socket-handlers/docker-socket-handler.ts#L324-L358","documentation":"ValidationError from saveStack: the composeYAML argument must be a string because it is written verbatim to the stack's compose.yaml file. Non-string values (objects produced by YAML editors, null, undefined) are rejected before any file I/O.","triggerScenarios":"Emitting 'create'/'stack' with composeYAML as null (editor failed to serialize), an object (parsed YAML sent instead of raw text), or undefined when the YAML pane never initialized.","commonSituations":"Sending CodeMirror/monaco editor state object instead of its string content; YAML parsed client-side with js-yaml before sending; fresh-stack creation with no default YAML string.","solutions":["Send the raw YAML text (editor.getValue()) not the editor instance or parsed object","Default to an empty string '' or a minimal compose template when no YAML exists","Validate typeof yaml === 'string' before emitting","Never run the YAML through a parser before sending — the server expects source text"],"exampleFix":"// before\nsocket.emit(\"create\", name, YAML.parse(editorContent), env, isAdd, cb); // object\n// after\nsocket.emit(\"create\", name, editorContent, env, isAdd, cb); // raw string","handlingStrategy":"validation","validationCode":"function assertComposeYAML(yaml: unknown): asserts yaml is string {\n  if (typeof yaml !== \"string\") throw new TypeError(\"composeYAML must be raw YAML text, not a parsed object\");\n}","typeGuard":"const isYamlString = (v: unknown): v is string => typeof v === \"string\";","tryCatchPattern":"try {\n  assertComposeYAML(editorContent);\n  socket.emit(\"create\", name, editorContent, env, isAdd, cb);\n} catch (e) { showError((e as Error).message); }","preventionTips":["Use editor.getValue() to send raw text, never the editor instance or parsed YAML","Initialize the YAML editor with '' or a template, not undefined","Do not YAML.parse payloads before sending them to Dockge"],"tags":["validation","socket-io","yaml","type-error"],"backgroundTag":"invalid-argument-type","analyzedSha":"f809ae192b571944ad773e9866d3e67064ae8043","analyzedAt":"2026-08-31T19:13:50.919Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}