superhuman: draft-management commands fail against the live backend
The superhuman CLI's draft-management surface does not interoperate with the current Superhuman backend. Reads (Gmail passthrough: messages list, messages query) and threads list work fine, and drafts new returns 200 — but drafts list, drafts get, and drafts discard all fail. Because drafts new "succeeds" while the read/list/discard paths fail, created drafts are effectively unmanageable.
Environment
superhuman-pp-cli built from library/productivity/superhuman (pseudo-version v0.0.0-20260618052343-6f215c39f277, runtime 2026.6.1)
- Auth via
auth login --disk (windowless Chrome-session mint — works well)
- Verified live against a real Superhuman account on 2026-06-18 and 2026-06-20 (identical results both days, so not a transient outage)
1. drafts list → HTTP 400 (root-caused + fix in PR)
$ superhuman-pp-cli drafts list --account <me> --agent
Error: POST /v3/userdata.getThreads returned HTTP 400: {"code":400}
Root cause: internal/cli/drafts_list.go posts an empty {} body to /v3/userdata.getThreads. The backend's BackendListFetcher requires a {filter:{type:"draft"}} discriminator — the exact lesson internal/cli/threads_list.go already encodes as PATCH(U3) (empty {} returns HTTP 400 from the backend). threads list --type draft (which sends that shape) succeeds live; drafts list 400s only because it never got the same shape.
Fix: send {filter:{type:"draft"}, limit, offset}. Verified live (released binary → 400; patched → 200 with the full draft threadList) + regression test. See PR.
2. drafts get <id> → empty / not-found
$ superhuman-pp-cli drafts get <draft-id> --account <me> --agent
Error: drafts get: <draft-id> not found
/v3/userdata.read returns no draft body in any of the wrapper shapes unmarshalDraftValue tries, even for a draft that exists (visible in threads list --type draft and in Gmail in:drafts). Note drafts get/readDraft build users/<uid>/threads/<draft-id>/messages/<draft-id>/draft reusing one id for both the thread and message segments, whereas real drafts have distinct thread-id vs message-id segments (e.g. threads/draft007cf1…/messages/draft00f93…) — a likely contributor. Needs the live userdata.read draft contract to fix confidently.
3. drafts discard <id> → HTTP 520
$ superhuman-pp-cli drafts discard <draft-id> --yes --account <me>
Error: POST /v3/userdata.write returned HTTP 520: {"code":520,"detail":"server-error"}
Persistent across ~10 retries over several minutes, on two separate days. drafts new uses the same userdata.write endpoint and returns 200, so the discard tombstone payload specifically is rejected at origin. Needs the live userdata.write delete/tombstone contract.
Secondary (separate bug): time-bomb test
internal/cli/send_test.go TestSendDryRun_ScheduleAt fails once the hardcoded 2026-05-20T08:00:00Z schedule date is in the past (send: --schedule-at must be in the future). go test ./... is red on main for this reason as of 2026-06-20. Worth switching to a relative future offset.
I've opened a PR for #1. Happy to help reproduce #2/#3 if useful, but they need the real backend contract to fix correctly.
superhuman: draft-management commands fail against the live backend
The
superhumanCLI's draft-management surface does not interoperate with the current Superhuman backend. Reads (Gmail passthrough:messages list,messages query) andthreads listwork fine, anddrafts newreturns 200 — butdrafts list,drafts get, anddrafts discardall fail. Becausedrafts new"succeeds" while the read/list/discard paths fail, created drafts are effectively unmanageable.Environment
superhuman-pp-clibuilt fromlibrary/productivity/superhuman(pseudo-versionv0.0.0-20260618052343-6f215c39f277, runtime2026.6.1)auth login --disk(windowless Chrome-session mint — works well)1.
drafts list→ HTTP 400 (root-caused + fix in PR)Root cause:
internal/cli/drafts_list.goposts an empty{}body to/v3/userdata.getThreads. The backend'sBackendListFetcherrequires a{filter:{type:"draft"}}discriminator — the exact lessoninternal/cli/threads_list.goalready encodes asPATCH(U3)(empty {} returns HTTP 400 from the backend).threads list --type draft(which sends that shape) succeeds live;drafts list400s only because it never got the same shape.Fix: send
{filter:{type:"draft"}, limit, offset}. Verified live (released binary → 400; patched → 200 with the full draftthreadList) + regression test. See PR.2.
drafts get <id>→ empty / not-found/v3/userdata.readreturns no draft body in any of the wrapper shapesunmarshalDraftValuetries, even for a draft that exists (visible inthreads list --type draftand in Gmailin:drafts). Notedrafts get/readDraftbuildusers/<uid>/threads/<draft-id>/messages/<draft-id>/draftreusing one id for both the thread and message segments, whereas real drafts have distinct thread-id vs message-id segments (e.g.threads/draft007cf1…/messages/draft00f93…) — a likely contributor. Needs the liveuserdata.readdraft contract to fix confidently.3.
drafts discard <id>→ HTTP 520Persistent across ~10 retries over several minutes, on two separate days.
drafts newuses the sameuserdata.writeendpoint and returns 200, so the discard tombstone payload specifically is rejected at origin. Needs the liveuserdata.writedelete/tombstone contract.Secondary (separate bug): time-bomb test
internal/cli/send_test.goTestSendDryRun_ScheduleAtfails once the hardcoded2026-05-20T08:00:00Zschedule date is in the past (send: --schedule-at must be in the future).go test ./...is red onmainfor this reason as of 2026-06-20. Worth switching to a relative future offset.I've opened a PR for #1. Happy to help reproduce #2/#3 if useful, but they need the real backend contract to fix correctly.