Summary
basecamp todos update <id> --due <date> reports success (ok: true, "Updated todo #...") but no fields are actually changed. The CLI sends the PUT to /{account}/todos/{id} instead of the documented /{account}/buckets/{project}/todos/{id}.json. The API returns 200 for that path but ignores the body, so the failure is completely silent.
Steps to reproduce
basecamp todos update 111111 --due 2026-09-23 --in 222222 --json
# → ok: true, summary: "Updated todo #111111"
basecamp todos show 111111 --in 222222 --jq '.data.due_on'
# → "2026-09-15" (unchanged)
Same result when passing the full app URL as the argument instead of a bare ID + --in.
Trace
-vv shows the request path (note: no /buckets/222222/):
[0.001s] Calling Todos.Update
[0.022s] -> PUT https://3.basecampapi.com/999999/todos/111111
[0.249s] <- 200 (226ms)
The 200 response even echoes the full todo (with the old due_on but a fresh updated_at), which the CLI treats as confirmation.
Expected
PUT https://3.basecampapi.com/999999/buckets/222222/todos/111111.json
Per the BC3 API docs, todos are updated at /buckets/{bucket}/todos/{id}.json.
Workaround
Raw API with the correct path works:
basecamp api put "buckets/222222/todos/111111.json" \
-d '{"content":"<existing title>","due_on":"2026-09-23"}'
# → due_on updated correctly
Environment
- basecamp CLI 0.7.2
- macOS (Darwin 27.0.0), arm64
- BC3 account, authenticated via OAuth
Notes
- Impact is worse than a normal bug because the exit code is 0 and the envelope says
ok: true — scripts and agents have no way to detect the failure without re-fetching the todo.
- Possibly the resource path builder for
Todos.Update drops the bucket scope; other verbs (todos show, done) resolve it correctly.
(IDs anonymised: 999999 = account, 222222 = project, 111111 = todo.)
Summary
basecamp todos update <id> --due <date>reports success (ok: true, "Updated todo #...") but no fields are actually changed. The CLI sends the PUT to/{account}/todos/{id}instead of the documented/{account}/buckets/{project}/todos/{id}.json. The API returns 200 for that path but ignores the body, so the failure is completely silent.Steps to reproduce
Same result when passing the full app URL as the argument instead of a bare ID +
--in.Trace
-vvshows the request path (note: no/buckets/222222/):The 200 response even echoes the full todo (with the old
due_onbut a freshupdated_at), which the CLI treats as confirmation.Expected
Per the BC3 API docs, todos are updated at
/buckets/{bucket}/todos/{id}.json.Workaround
Raw API with the correct path works:
Environment
Notes
ok: true— scripts and agents have no way to detect the failure without re-fetching the todo.Todos.Updatedrops the bucket scope; other verbs (todos show,done) resolve it correctly.(IDs anonymised:
999999= account,222222= project,111111= todo.)