Body
Version: gws v0.22.5 (Windows x86_64 binary from GitHub Releases)
Environment:
- OS: Windows 10/11
- Invocation:
cmd /c gws ...
- Auth: OAuth user token via
gws auth login --scopes <explicit URI list>
- Shared Drive in use: Yes
Summary
Several gws commands exit with code 0 and no stderr error, but write nothing to stdout, on API calls that should return data. Cross-checks against the same Workspace account via Google's APIs and via alternative gws command forms confirm the underlying data exists — the response is being produced by the API and then silently dropped by gws before reaching stdout.
This is distinct from legitimate empty-result cases (where gws correctly writes {} or an empty items array).
Affected endpoints (confirmed)
All reproduced against the same authenticated session.
| Endpoint |
Symptom |
Cross-check confirming data exists |
gws people people connections list |
empty stdout, exit 0 |
Account has contacts — confirmed via people contactGroups get with maxMembers returning populated memberResourceNames |
gws people people searchContacts |
empty stdout, exit 0 |
searchContacts with a known contact email returns empty even though the contact exists |
gws gmail users labels list |
empty stdout, exit 0 |
8 user labels exist (verified independently) |
gws gmail +read --message-id <ID> |
empty stdout, exit 0; stderr notes "unknown output format 'text', falling back to json" but nothing follows |
Same message ID returns full content via gws gmail users messages get |
gws gmail users messages get for a specific message ID |
empty stdout, exit 0 |
Same command with a different message ID returns the full multipart message correctly |
Reproduction
Case A: gmail users labels list
gws gmail users labels list --params "{\"userId\":\"me\"}"
Expected: JSON object containing labels array with all user and system labels.
Actual: Exit 0. Empty stdout. Stderr shows only the cosmetic Using keyring backend: keyring line.
Case B: gmail users messages get — ID-specific
gws gmail users messages get --params "{\"userId\":\"me\",\"id\":\"<id>\",\"format\":\"full\"}"
Expected: Full message resource (headers, body parts, snippet).
Actual: Exit 0. Empty stdout.
Contrast: The same command with a different message ID (from the same account, same token) returns a complete multipart message resource. The only difference is the message ID.
Case C: gmail +read
gws gmail +read --message-id <id>
Expected: Message text rendered to stdout.
Actual: Exit 0. Empty stdout. Stderr includes unknown output format 'text', falling back to json — but no JSON follows.
Case D: people people searchContacts
gws people people searchContacts --params "{\"query\":\"<term>\",\"readMask\":\"names,emailAddresses\"}"
Expected: JSON with search results matching known contacts.
Actual: Exit 0. Empty stdout.
Hypothesis
The failure mode looks like a swallowed serialization error. Evidence:
- The
+read case emits unknown output format 'text', falling back to json to stderr before producing empty stdout — the output pipeline is entering a fallback path that then fails silently.
- The
messages get case is ID-specific, not command-specific — which suggests sensitivity to response shape (e.g., specific multipart structures, header configurations, or MIME types present in one message but not another).
- The broken endpoints share a common feature: they return variable-shape payloads.
labels list returns a heterogeneous list (system and user labels have different field sets). connections list and searchContacts return Person objects with optional fields whose presence depends on the contact. Meanwhile, the endpoints that work reliably return shapes that are stable per-call.
A reasonable guess: the output formatter panics or errors on certain response shapes and the error is caught and discarded (e.g., via .ok() or let _ = ...) rather than surfaced.
Impact
Critical for automation. Any script, skill, or agent that treats exit 0 + empty stdout as "no results" will:
- Miss Gmail labels entirely on a fresh read
- Fail to retrieve certain messages
- Skip contact searches entirely
- Produce wrong, not just slow, downstream behavior
Requested behavior
- Never emit empty stdout on a successful API call. Even for a genuinely empty result, return a structurally valid JSON envelope (
{"items": []}, {"labels": []}, etc.) that downstream consumers can parse.
- Exit non-zero when output serialization fails. If the formatter cannot produce output for a response it received, that is an error condition — exit with a distinct non-zero code and print a descriptive message to stderr including the endpoint and relevant debug information.
- Consider a
--raw or --debug-response flag that bypasses the formatter and prints the raw API response, so users can recover data even when the formatter fails on a specific payload shape.
Proposed fix direction
Audit the output-formatting pipeline for silently-caught errors. In Rust, this often looks like .ok() or let _ = ... on a Result where ? or .unwrap_or_else(|e| log_and_exit(e)) would be correct. A grep for .ok() on serialization or formatting results would be a reasonable starting point.
Integration tests that would catch this class of bug:
labels list against an account with at least one user label (assert: exit 0 AND stdout is non-empty AND parses as JSON AND .labels | length >= 1)
messages get against a message with a complex multipart structure
searchContacts with a query matching at least one known contact
Body
Version:
gwsv0.22.5 (Windows x86_64 binary from GitHub Releases)Environment:
cmd /c gws ...gws auth login --scopes <explicit URI list>Summary
Several
gwscommands exit with code 0 and no stderr error, but write nothing to stdout, on API calls that should return data. Cross-checks against the same Workspace account via Google's APIs and via alternativegwscommand forms confirm the underlying data exists — the response is being produced by the API and then silently dropped bygwsbefore reaching stdout.This is distinct from legitimate empty-result cases (where
gwscorrectly writes{}or an empty items array).Affected endpoints (confirmed)
All reproduced against the same authenticated session.
gws people people connections listpeople contactGroups getwithmaxMembersreturning populatedmemberResourceNamesgws people people searchContactssearchContactswith a known contact email returns empty even though the contact existsgws gmail users labels listgws gmail +read --message-id <ID>"unknown output format 'text', falling back to json"but nothing followsgws gmail users messages getgws gmail users messages getfor a specific message IDReproduction
Case A:
gmail users labels listExpected: JSON object containing
labelsarray with all user and system labels.Actual: Exit 0. Empty stdout. Stderr shows only the cosmetic
Using keyring backend: keyringline.Case B:
gmail users messages get— ID-specificExpected: Full message resource (headers, body parts, snippet).
Actual: Exit 0. Empty stdout.
Contrast: The same command with a different message ID (from the same account, same token) returns a complete multipart message resource. The only difference is the message ID.
Case C:
gmail +readExpected: Message text rendered to stdout.
Actual: Exit 0. Empty stdout. Stderr includes
unknown output format 'text', falling back to json— but no JSON follows.Case D:
people people searchContactsExpected: JSON with search results matching known contacts.
Actual: Exit 0. Empty stdout.
Hypothesis
The failure mode looks like a swallowed serialization error. Evidence:
+readcase emitsunknown output format 'text', falling back to jsonto stderr before producing empty stdout — the output pipeline is entering a fallback path that then fails silently.messages getcase is ID-specific, not command-specific — which suggests sensitivity to response shape (e.g., specific multipart structures, header configurations, or MIME types present in one message but not another).labels listreturns a heterogeneous list (system and user labels have different field sets).connections listandsearchContactsreturnPersonobjects with optional fields whose presence depends on the contact. Meanwhile, the endpoints that work reliably return shapes that are stable per-call.A reasonable guess: the output formatter panics or errors on certain response shapes and the error is caught and discarded (e.g., via
.ok()orlet _ = ...) rather than surfaced.Impact
Critical for automation. Any script, skill, or agent that treats
exit 0 + empty stdoutas "no results" will:Requested behavior
{"items": []},{"labels": []}, etc.) that downstream consumers can parse.--rawor--debug-responseflag that bypasses the formatter and prints the raw API response, so users can recover data even when the formatter fails on a specific payload shape.Proposed fix direction
Audit the output-formatting pipeline for silently-caught errors. In Rust, this often looks like
.ok()orlet _ = ...on aResultwhere?or.unwrap_or_else(|e| log_and_exit(e))would be correct. A grep for.ok()on serialization or formatting results would be a reasonable starting point.Integration tests that would catch this class of bug:
labels listagainst an account with at least one user label (assert: exit 0 AND stdout is non-empty AND parses as JSON AND.labels | length >= 1)messages getagainst a message with a complex multipart structuresearchContactswith a query matching at least one known contact