{"record":{"id":"1ee7694ce171bb4a","repo":"cross-rs/cross","slug":"output-tag-contains-newlines-consider-serializing-with-json","errorCode":null,"errorMessage":"output `{tag}` contains newlines, consider serializing with json and deserializing in gha with fromJSON()","messagePattern":"output `(.+?)` contains newlines, consider serializing with json and deserializing in gha with fromJSON\\(\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"xtask/src/util.rs","lineNumber":322,"sourceCode":"        eprintln!($fmt $(,$args)*);\n    };\n}\n\n// note: for GHA actions we need to output these tags no matter the verbosity level\npub fn gha_print(content: &str) {\n    gha_output!(\"{}\", content);\n}\n\n// note: for GHA actions we need to output these tags no matter the verbosity level\npub fn gha_error(content: &str) {\n    gha_output!(\"::error {}\", content);\n}\n\n#[track_caller]\npub fn gha_output(tag: &str, content: &str) -> cross::Result<()> {\n    if content.contains('\\n') {\n        // https://github.com/actions/toolkit/issues/403\n        eyre::bail!(\n            \"output `{tag}` contains newlines, consider serializing with json and deserializing in gha with fromJSON()\"\n        );\n    }\n    write_to_gha_env_file(\"GITHUB_OUTPUT\", &format!(\"{tag}={content}\"))?;\n    Ok(())\n}\n\npub fn read_dockerfiles(msg_info: &mut MessageInfo) -> cross::Result<Vec<(PathBuf, String)>> {\n    let root = project_dir(msg_info)?;\n    let docker = root.join(\"docker\");\n    let mut dockerfiles = vec![];\n    for entry in fs::read_dir(docker)? {\n        let entry = entry?;\n        let file_type = entry.file_type()?;\n        let file_name = entry.file_name();\n        if file_type.is_file() && file_name.to_utf8()?.starts_with(\"Dockerfile\") {\n            let contents = fs::read_to_string(entry.path())?;\n            dockerfiles.push((entry.path().to_path_buf(), contents));","sourceCodeStart":304,"sourceCodeEnd":340,"githubUrl":"https://github.com/cross-rs/cross/blob/8c1a8aa4b661711f4b7b6ac07c2e8929ce2f7d27/xtask/src/util.rs#L304-L340","documentation":"gha_output writes a `tag=content` line to the GITHUB_OUTPUT environment file so later GitHub Actions steps can read the value. GitHub Actions environment files cannot contain raw multiline values (actions/toolkit#403), so this function proactively rejects any content containing a newline ('\\n') instead of silently producing a corrupted workflow output. The caller is expected to serialize the value as JSON and read it back with fromJSON() in the workflow.","triggerScenarios":"Calling gha_output(tag, content) with a content string that contains a '\\n' character, e.g. multi-line tool output, a multi-line version list, or text built with format! over several lines.","commonSituations":"A maintainer adds a CI task (via the ci, build_docker_image, or run xtask commands) that emits multi-line output such as a list of matrix entries, test names, or docker tags; locally or in a runner the collected content ends up multiline and the xtask step fails.","solutions":["Serialize the multi-line value with serde_json::to_string (or json!([...])) before passing it to gha_output, then in the workflow read it with fromJSON(steps.<step>.outputs.<tag>).","Join lines with a single-line delimiter (e.g. space or comma) if JSON round-tripping is overkill.","Split the content into multiple scalar outputs (one gha_output call per line item) if the consumer only needs individual values."],"exampleFix":"// before\nlet targets = targets.join(\"\\n\");\ngha_output(\"targets\", &targets)?;\n\n// after\nlet targets = serde_json::to_string(&targets)?;\ngha_output(\"targets\", &targets)?;\n// workflow: ${{ fromJSON(steps.build.outputs.targets)[0] }}","handlingStrategy":"validation","validationCode":"fn gha_output_safe(tag: &str, content: &str) -> cross::Result<()> {\n    if content.contains('\\n') {\n        eyre::bail!(\"refusing to write multi-line output `{tag}`; serialize as JSON first\");\n    }\n    Ok(())\n}\n// call gha_output_safe(tag, &content)?; before gha_output","typeGuard":"fn is_single_line(s: &str) -> bool { !s.contains('\\n') }","tryCatchPattern":null,"preventionTips":["Always JSON-serialize list/multi-line values before passing to gha_output.","Add a unit test asserting outputs passed to gha_output contain no '\\n'.","Prefer building outputs from typed structs serialized with serde_json rather than hand-joined strings."],"tags":["github-actions","ci","newline","output-serialization"],"backgroundTag":"invalid-argument-value","analyzedSha":"8c1a8aa4b661711f4b7b6ac07c2e8929ce2f7d27","analyzedAt":"2026-09-13T15:10:43.988Z","contentChangedAt":"2026-09-13T15:10:43.988Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}