You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
🤖 Drafted with agent assistance; filed by @jeremy.
Summary
Every basecamp todos update invocation silently clears the to-do's completion subscribers ("When done, notify…"). Both code paths in newTodosUpdateCmd (internal/commands/todos.go:939) build their PUT body without completion_subscriber_ids, and the BC3 todos update endpoint has replace semantics — omitting a parameter clears it (bc3-api, todos: "Omitting a parameter will clear its value").
The CLI already knows about this server contract — #412/#413 added a read-modify-write merge precisely to stop partial PUTs from wiping untouched fields — but the merge preserves only content, description, due_on, starts_on, and assignee_ids. Completion subscribers were never included, because the SDK's Todo model doesn't map the completion_subscribers field at all (basecamp/basecamp-sdk#355), so the merge has nothing to preserve. git grep -i completion_subscriber across internal/ and cmd/ returns zero hits.
Reproduction
In Basecamp, create a to-do and add a person under "When done, notify…"
basecamp todos update <id> --title "New title" --in <project>
Reload the to-do in Basecamp: the completion subscriber is gone. No warning, exit 0.
Applies equally to the field-clearing path (--no-due, --no-description, --due ""), which hand-builds a raw PUT body (internal/commands/todos.go:1043) that likewise omits completion_subscriber_ids.
Root cause chain
Layer
Defect
BC3 API
Documented replace semantics: omitted field = cleared (long-established; not changing)
SDK
Todo model omits completion_subscribers → clients cannot read them to preserve them (basecamp/basecamp-sdk#355); Todos.Update itself is a partial PUT with the same wipe hazard on every field (basecamp/basecamp-sdk#372)
CLI
Read-modify-write merge (both branches of newTodosUpdateCmd) preserves five fields, not six — subscribers fall through
Context: this same field was among those wiped in two production support incidents (June, July 2026) caused by third-party scripts using the Go SDK's partial-PUT Update — see basecamp/basecamp-sdk#372 for the fleet-wide SDK fix (merge-safe Update, new Edit read-modify-write closure, explicit Replace). The CLI is the interim mitigation for its own users until that lands, which makes this gap in its merge worth closing now rather than waiting.
Fix plan
Phase 1 — close the gap now (this issue)
Preserve completion subscribers in both branches of newTodosUpdateCmd:
Fetch the current to-do's completion_subscribers ids. The SDK model doesn't carry them (Stamp plugin version at release time #355), so until it does, read them from the raw GET JSON — the CLI already has app.Account().Get/raw request plumbing (same style the needsClear branch uses for its raw PUT). The BC3 payload field is completion_subscribers, an array of person objects; collect .id.
Include completion_subscriber_ids in the PUT body in both branches, defaulting to the fetched ids (preserve). No new flag is required to fix the wipe.
Optional, natural companion (mirrors --assignee): --notify-on-completion <names-or-ids> and --no-notify-on-completion to set/clear explicitly, reusing resolveAssigneeIDs-style person resolution. If added, follow the existing conflict-detection pattern (--no-X vs --X usage error, internal/commands/todos.go:969).
Test: update a to-do that has completion subscribers via --title only → subscribers unchanged; via --no-due → subscribers unchanged; explicit clear (if flag added) → cleared. The e2e harness's existing todos update coverage is the place to extend.
Replace the hand-rolled merge (internal/commands/todos.go:1126 branch) and the needsClear raw-PUT workaround (internal/commands/todos.go:1029 branch, ~80 lines) with the SDK's Edit closure — both branches collapse into one: fetch-free flag mapping onto Edit, with --no-X flags becoming plain field-clears on the full representation.
🤖 Drafted with agent assistance; filed by @jeremy.
Summary
Every
basecamp todos updateinvocation silently clears the to-do's completion subscribers ("When done, notify…"). Both code paths innewTodosUpdateCmd(internal/commands/todos.go:939) build their PUT body withoutcompletion_subscriber_ids, and the BC3 todos update endpoint has replace semantics — omitting a parameter clears it (bc3-api, todos: "Omitting a parameter will clear its value").The CLI already knows about this server contract — #412/#413 added a read-modify-write merge precisely to stop partial PUTs from wiping untouched fields — but the merge preserves only
content,description,due_on,starts_on, andassignee_ids. Completion subscribers were never included, because the SDK'sTodomodel doesn't map thecompletion_subscribersfield at all (basecamp/basecamp-sdk#355), so the merge has nothing to preserve.git grep -i completion_subscriberacrossinternal/andcmd/returns zero hits.Reproduction
basecamp todos update <id> --title "New title" --in <project>Applies equally to the field-clearing path (
--no-due,--no-description,--due ""), which hand-builds a raw PUT body (internal/commands/todos.go:1043) that likewise omitscompletion_subscriber_ids.Root cause chain
Todomodel omitscompletion_subscribers→ clients cannot read them to preserve them (basecamp/basecamp-sdk#355);Todos.Updateitself is a partial PUT with the same wipe hazard on every field (basecamp/basecamp-sdk#372)newTodosUpdateCmd) preserves five fields, not six — subscribers fall throughContext: this same field was among those wiped in two production support incidents (June, July 2026) caused by third-party scripts using the Go SDK's partial-PUT
Update— see basecamp/basecamp-sdk#372 for the fleet-wide SDK fix (merge-safeUpdate, newEditread-modify-write closure, explicitReplace). The CLI is the interim mitigation for its own users until that lands, which makes this gap in its merge worth closing now rather than waiting.Fix plan
Phase 1 — close the gap now (this issue)
Preserve completion subscribers in both branches of
newTodosUpdateCmd:completion_subscribersids. The SDK model doesn't carry them (Stamp plugin version at release time #355), so until it does, read them from the raw GET JSON — the CLI already hasapp.Account().Get/raw request plumbing (same style theneedsClearbranch uses for its raw PUT). The BC3 payload field iscompletion_subscribers, an array of person objects; collect.id.completion_subscriber_idsin the PUT body in both branches, defaulting to the fetched ids (preserve). No new flag is required to fix the wipe.--assignee):--notify-on-completion <names-or-ids>and--no-notify-on-completionto set/clear explicitly, reusingresolveAssigneeIDs-style person resolution. If added, follow the existing conflict-detection pattern (--no-Xvs--Xusage error,internal/commands/todos.go:969).--titleonly → subscribers unchanged; via--no-due→ subscribers unchanged; explicit clear (if flag added) → cleared. The e2e harness's existing todos update coverage is the place to extend.Phase 2 — after basecamp/basecamp-sdk#372 lands (follow-up, tracked here for the map)
internal/commands/todos.go:1126branch) and theneedsClearraw-PUT workaround (internal/commands/todos.go:1029branch, ~80 lines) with the SDK'sEditclosure — both branches collapse into one: fetch-free flag mapping ontoEdit, with--no-Xflags becoming plain field-clears on the full representation.completion_subscribers(Todo model omits completion_subscribers, description_attachments, comments_count basecamp-sdk#355).Phase 1 is deliberately shaped so Phase 2 deletes it cleanly: the ids ride the same merge that already exists, no new abstractions.
Acceptance criteria
basecamp todos update <id> --title "x"leaves completion subscribers untouched (both merge and clear branches)content,description,due_on,starts_on,assignee_ids) still preserved — regression-coveredRelated
Update/Edit/Replace); Phase 2 dependencyTodomodel missingcompletion_subscribers; blocks doing this via typed SDK calls