Body
Version: gws v0.22.5 (Windows x86_64 binary from GitHub Releases)
Environment:
- OS: Windows 10/11
- Invocation:
cmd /c gws ...
Related: See also #714, which documents a separate --output failure mode (silent non-write when the API returns JSON-wrapped base64). The sub-issues below are distinct from #714 but share the same flag surface.
Summary
Four distinct file-path handling behaviors observed across gws subcommands, all undocumented in --help output, and all inconsistent with typical CLI conventions. They are grouped here because they share a common root — gws treats file paths and output handling as implementation details with inconsistent contracts across subcommands.
4a. gws drive files get --output <path> rejects any path that is not relative to gws's current working directory. Absolute paths — even to normal writable locations like the user's temp directory — return exit code 3 (validation error).
4b. gws drive files export does not stream content to stdout and does not honor --output. It always writes to a file named download.txt in the current working directory, and prints a status JSON object to stdout instead of the content.
4c. gws drive files create --upload <path> has the same cwd restriction as --output. Uploading a file from outside gws's cwd returns exit 3 with a "resolves to ... which is outside the current directory" validation error.
4d. gws calendar events delete mis-routes the 204 No Content response into the same to-file save path as drive files export. It writes an empty download.html to cwd and emits a synthesized status JSON to stdout. This behavior is endpoint-specific: other 204 endpoints (gmail users labels delete, gmail users drafts delete, gmail users messages trash, tasks tasklists delete, people people deleteContact) all return clean empty or {} stdout without any file artifact.
Reproduction
4a: --output absolute path rejection
gws drive files get --file-id <ID> --params "{\"alt\":\"media\"}" --output C:\Users\<user>\AppData\Local\Temp\file.pdf
Expected: File saved to the specified absolute path.
Actual: Exit code 3. Validation error. No file written.
Workaround: Use a relative filename — file is written to gws's cwd; consumer must move it.
4b: files export saves to download.txt regardless of flag or intent
gws drive files export --file-id <ID> --params "{\"mimeType\":\"text/plain\"}"
Expected (option 1): Exported text content on stdout.
Expected (option 2): File written to a consumer-specified path via --output, with status on stderr.
Actual: Stdout contains:
{"bytes": 5234, "mimeType": "text/plain", "saved_file": "download.txt", "status": "success"}
The exported content is in download.txt in gws's cwd — created without any user-supplied path.
4c: --upload cwd restriction
gws drive files create --params "{\"supportsAllDrives\":true}" --json "{\"name\":\"test.txt\",\"parents\":[\"<folder-id>\"]}" --upload D:/some/path/file.txt
Expected: File uploaded from specified absolute path.
Actual: Exit code 3:
{"error": {"code": 400, "message": "--upload 'D:/some/path/file.txt' resolves to '\\\\?\\D:\\some\\path\\file.txt' which is outside the current directory", "reason": "validationError"}}
Workaround: Copy the file into gws's cwd first, then use a relative filename.
4d: events delete creates spurious download.html on 204
gws calendar events delete --params "{\"calendarId\":\"primary\",\"eventId\":\"<id>\",\"sendUpdates\":\"none\"}"
Expected: Clean 204 response — empty stdout or a minimal {} success marker, no file artifacts.
Actual: Exit 0, but stdout contains:
{"bytes": 0, "mimeType": "text/html", "saved_file": "download.html", "status": "success"}
And an empty download.html file is created in gws's cwd.
Contrast with other 204 endpoints in the same session:
| Endpoint |
stdout on 204 |
Side-effect file |
gmail users labels delete |
empty |
none |
gmail users drafts delete |
empty |
none |
gmail users settings filters delete |
empty |
none |
tasks tasklists delete |
empty |
none |
people people deleteContact |
{} |
none |
calendar events delete |
status JSON with saved_file |
download.html |
Hypothesis
The output pipeline appears to branch on response characteristics — if the content-type header suggests binary, or if a 204 is received on certain endpoints, the response is routed through a "save to file" path that writes download.{ext} to cwd and returns a status JSON to stdout. This branch is triggered inappropriately for:
files export responses (always — arguably intended for binary exports, but the default should be stdout streaming).
calendar events delete 204 responses — likely because the empty 204 carries a default text/html content-type header, which trips the binary-save branch. Other delete endpoints appear to reach a different code path.
The cwd restrictions on --output and --upload (4a, 4c) appear to be a path-validation function applied to any flag that accepts a file path. There may be a safety intent here, but the implementation is over-broad — rejecting absolute paths within the user's own home or temp directory is not meaningful protection.
Impact
- cwd management becomes the consumer's job. Scripts calling
drive files get, drive files export, drive files create --upload, or calendar events delete must track gws's cwd, watch for output files appearing there, and move or clean them up.
- Collision risk. Shared
download.txt / download.html names mean two simultaneous operations overwrite each other's output.
- Inconsistency across 204 endpoints. Consumers cannot rely on a uniform "delete = empty stdout" contract.
- Not pipe-friendly.
files export content cannot be piped directly to another command.
- Upload friction for automation.
--upload cwd restriction forces a copy-into-cwd step for pipelines where the source file is in a generated or fixed location.
Requested behavior
4a / 4c: Accept absolute paths in --output and --upload. There is no safety justification for restricting these to cwd — the user ran the command and chose the path. If preventing accidental overwrite is the intent, a confirmation prompt on existing-file would be more appropriate than a blanket rejection.
4b: Default files export to streaming content to stdout. Respect --output if provided. Document the behavior in --help.
4d: Route all 204 No Content responses through the same handler regardless of endpoint. calendar events delete should produce the same clean empty stdout that gmail users labels delete and tasks tasklists delete produce — no download.html, no status JSON.
Proposed fix direction
- Locate the
--output / --upload path validator and remove the cwd-relativity check. Resolve to canonical form and accept any writable path.
- Audit 204 response handling: confirm
calendar events delete is hitting the "save binary to file" branch and trace why. The branch likely keys on content-type; events delete may be tripping it because the empty 204 carries a text/html content-type header from the server. Fix the branch condition to also require a non-empty body before saving to file.
- For
files export: change default to stdout streaming; keep --output as the opt-in for file saving.
Body
Version:
gwsv0.22.5 (Windows x86_64 binary from GitHub Releases)Environment:
cmd /c gws ...Related: See also #714, which documents a separate
--outputfailure mode (silent non-write when the API returns JSON-wrapped base64). The sub-issues below are distinct from #714 but share the same flag surface.Summary
Four distinct file-path handling behaviors observed across
gwssubcommands, all undocumented in--helpoutput, and all inconsistent with typical CLI conventions. They are grouped here because they share a common root —gwstreats file paths and output handling as implementation details with inconsistent contracts across subcommands.4a.
gws drive files get --output <path>rejects any path that is not relative togws's current working directory. Absolute paths — even to normal writable locations like the user's temp directory — return exit code 3 (validation error).4b.
gws drive files exportdoes not stream content to stdout and does not honor--output. It always writes to a file nameddownload.txtin the current working directory, and prints a status JSON object to stdout instead of the content.4c.
gws drive files create --upload <path>has the same cwd restriction as--output. Uploading a file from outsidegws's cwd returns exit 3 with a"resolves to ... which is outside the current directory"validation error.4d.
gws calendar events deletemis-routes the 204 No Content response into the same to-file save path asdrive files export. It writes an emptydownload.htmlto cwd and emits a synthesized status JSON to stdout. This behavior is endpoint-specific: other 204 endpoints (gmail users labels delete,gmail users drafts delete,gmail users messages trash,tasks tasklists delete,people people deleteContact) all return clean empty or{}stdout without any file artifact.Reproduction
4a:
--outputabsolute path rejectionExpected: File saved to the specified absolute path.
Actual: Exit code 3. Validation error. No file written.
Workaround: Use a relative filename — file is written to
gws's cwd; consumer must move it.4b:
files exportsaves todownload.txtregardless of flag or intentExpected (option 1): Exported text content on stdout.
Expected (option 2): File written to a consumer-specified path via
--output, with status on stderr.Actual: Stdout contains:
{"bytes": 5234, "mimeType": "text/plain", "saved_file": "download.txt", "status": "success"}The exported content is in
download.txtingws's cwd — created without any user-supplied path.4c:
--uploadcwd restrictionExpected: File uploaded from specified absolute path.
Actual: Exit code 3:
{"error": {"code": 400, "message": "--upload 'D:/some/path/file.txt' resolves to '\\\\?\\D:\\some\\path\\file.txt' which is outside the current directory", "reason": "validationError"}}Workaround: Copy the file into
gws's cwd first, then use a relative filename.4d:
events deletecreates spuriousdownload.htmlon 204Expected: Clean 204 response — empty stdout or a minimal
{}success marker, no file artifacts.Actual: Exit 0, but stdout contains:
{"bytes": 0, "mimeType": "text/html", "saved_file": "download.html", "status": "success"}And an empty
download.htmlfile is created ingws's cwd.Contrast with other 204 endpoints in the same session:
gmail users labels deletegmail users drafts deletegmail users settings filters deletetasks tasklists deletepeople people deleteContact{}calendar events deletesaved_filedownload.htmlHypothesis
The output pipeline appears to branch on response characteristics — if the content-type header suggests binary, or if a 204 is received on certain endpoints, the response is routed through a "save to file" path that writes
download.{ext}to cwd and returns a status JSON to stdout. This branch is triggered inappropriately for:files exportresponses (always — arguably intended for binary exports, but the default should be stdout streaming).calendar events delete204 responses — likely because the empty 204 carries a defaulttext/htmlcontent-type header, which trips the binary-save branch. Other delete endpoints appear to reach a different code path.The cwd restrictions on
--outputand--upload(4a, 4c) appear to be a path-validation function applied to any flag that accepts a file path. There may be a safety intent here, but the implementation is over-broad — rejecting absolute paths within the user's own home or temp directory is not meaningful protection.Impact
drive files get,drive files export,drive files create --upload, orcalendar events deletemust trackgws's cwd, watch for output files appearing there, and move or clean them up.download.txt/download.htmlnames mean two simultaneous operations overwrite each other's output.files exportcontent cannot be piped directly to another command.--uploadcwd restriction forces a copy-into-cwd step for pipelines where the source file is in a generated or fixed location.Requested behavior
4a / 4c: Accept absolute paths in
--outputand--upload. There is no safety justification for restricting these to cwd — the user ran the command and chose the path. If preventing accidental overwrite is the intent, a confirmation prompt on existing-file would be more appropriate than a blanket rejection.4b: Default
files exportto streaming content to stdout. Respect--outputif provided. Document the behavior in--help.4d: Route all 204 No Content responses through the same handler regardless of endpoint.
calendar events deleteshould produce the same clean empty stdout thatgmail users labels deleteandtasks tasklists deleteproduce — nodownload.html, no status JSON.Proposed fix direction
--output/--uploadpath validator and remove the cwd-relativity check. Resolve to canonical form and accept any writable path.calendar events deleteis hitting the "save binary to file" branch and trace why. The branch likely keys on content-type;events deletemay be tripping it because the empty 204 carries atext/htmlcontent-type header from the server. Fix the branch condition to also require a non-empty body before saving to file.files export: change default to stdout streaming; keep--outputas the opt-in for file saving.