{"record":{"id":"dfb95d64acfde3c0","repo":"googleworkspace/cli","slug":"failed-to-open-upload-file","errorCode":null,"errorMessage":"failed to open upload file '{}': {}","messagePattern":"failed to open upload file '(.+?)': (.+?)","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/google-workspace-cli/src/executor.rs","lineNumber":899,"sourceCode":"        None => \"{}\".to_string(),\n    };\n\n    let preamble = format!(\n        \"--{boundary}\\r\\nContent-Type: application/json; charset=UTF-8\\r\\n\\r\\n{metadata_json}\\r\\n\\\n         --{boundary}\\r\\nContent-Type: {media_mime}\\r\\n\\r\\n\"\n    );\n    let postamble = format!(\"\\r\\n--{boundary}--\\r\\n\");\n\n    let content_length = preamble.len() as u64 + file_size + postamble.len() as u64;\n    let content_type = format!(\"multipart/related; boundary={boundary}\");\n\n    let preamble_bytes: bytes::Bytes = preamble.into_bytes().into();\n    let postamble_bytes: bytes::Bytes = postamble.into_bytes().into();\n\n    let file_path_owned = file_path.to_owned();\n    let file_stream = futures_util::stream::once(async move {\n        tokio::fs::File::open(&file_path_owned).await.map_err(|e| {\n            std::io::Error::new(\n                e.kind(),\n                format!(\"failed to open upload file '{}': {}\", file_path_owned, e),\n            )\n        })\n    })\n    .map_ok(tokio_util::io::ReaderStream::new)\n    .try_flatten();\n\n    let stream = futures_util::stream::once(async { Ok::<_, std::io::Error>(preamble_bytes) })\n        .chain(file_stream)\n        .chain(futures_util::stream::once(async {\n            Ok::<_, std::io::Error>(postamble_bytes)\n        }));\n\n    Ok((\n        reqwest::Body::wrap_stream(stream),\n        content_type,\n        content_length,","sourceCodeStart":881,"sourceCodeEnd":917,"githubUrl":"https://github.com/googleworkspace/cli/blob/a3768d0e82ad83cca2da97724e46bea4ff0e6dbd/crates/google-workspace-cli/src/executor.rs#L881-L917","documentation":"This io::Error is produced inside build_multipart_stream() when tokio::fs::File::open fails while starting the streaming multipart/related upload body. Because the open happens lazily inside futures_util::stream::once, the error surfaces at request-stream time (during reqwest .send()), not when the command arguments are parsed. The original io::ErrorKind (NotFound, PermissionDenied, etc.) is preserved and the path plus OS cause are embedded in the message.","triggerScenarios":"Passing a non-existent path to a media upload argument (kind NotFound); a path with no read permission (PermissionDenied); a directory instead of a file (IsADirectory/NotFound on some platforms); a file deleted between the earlier size stat and the body stream starting; a relative path resolved from a different working directory; a leading ~ that the shell did not expand because it was quoted.","commonSituations":"Uploading via gws drive files create --media from a script where the path was built by joining variables that are empty or wrong; quoting \"~/upload.pdf\" so tilde expansion never happens; running in a container where the file was not bind-mounted; case-sensitive filesystem mismatch after developing on macOS and deploying on Linux.","solutions":["Verify the exact path exists and is readable before invoking the command: ls -l on the literal string you pass","Use an absolute path (and expand ~ yourself: ${HOME}/upload.pdf) so the CLI's working directory cannot change resolution","Check file permissions (read bit for the running user) and that the path is a regular file, not a directory or symlink to a missing target","If the file may be written concurrently, upload from a snapshot/copy so it cannot disappear between size computation and streaming"],"exampleFix":"# before\ngws drive files create --media \"~/report.pdf\"   # ~ not expanded inside quotes\n\n# after\ngws drive files create --media \"$HOME/report.pdf\"","handlingStrategy":"validation","validationCode":"use tokio::fs;\n\nlet path = std::path::Path::new(media_path);\nlet meta = fs::metadata(path).await?;\nif !meta.is_file() {\n    anyhow::bail!(\"upload path is not a regular file: {media_path}\");\n}\nif meta.permissions().readonly() {\n    // readable check is what matters; on unix also verify read bit:\n    #[cfg(unix)]\n    {\n        use std::os::unix::fs::PermissionsExt;\n        assert!(meta.permissions().mode() & 0o400 != 0, \"no read permission\");\n    }\n}\n// Now safe to build the multipart upload","typeGuard":null,"tryCatchPattern":"// The open error surfaces as io::Error while the reqwest body streams.\nmatch execute(cmd).await {\n    Err(e) if e.to_string().contains(\"failed to open upload file\") => {\n        eprintln!(\"check that the --media path exists and is readable\");\n    }\n    other => other?,\n}","preventionTips":["Pass absolute paths and expand ~ in your own shell/script ($HOME/file) — the CLI does not expand tildes in quoted arguments","Validate existence + is_file + read permission immediately before invoking any upload command, especially in scripts that build paths from variables","For files being written concurrently, copy to a stable temporary location first so the file cannot vanish between the size stat and the streamed open"],"tags":["io","upload","multipart","file-path","streaming"],"backgroundTag":"file-open-failed","analyzedSha":"a3768d0e82ad83cca2da97724e46bea4ff0e6dbd","analyzedAt":"2026-08-16T19:51:46.516Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}