Skip to content

Fail when Basecamp drops a requested assignee - #981

Merged
robzolkos merged 3 commits into
mainfrom
assignee-not-silent
Oct 28, 2026
Merged

robzolkos merged 3 commits into
mainfrom
assignee-not-silent

Conversation

@robzolkos

@robzolkos robzolkos commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

basecamp cards create … --to <column id> exited 0 and created an unassigned card. On cards create, --to is the assignee, and Basecamp answers success while silently dropping any assignee_ids entry it can't assign on that recording. Every command that sets assignees had the same hole, and cards update/todos update were worse: they replace the set, so a bad ID cleared the real assignees too.

What bc3 does (measured against local bc3)

Command, bad assignee Before After
cards create --to <column id> exit 0, card unassigned exit 1, names card #, dropped ID
cards create --assignee <account person not on project> exit 0, unassigned exit 1
cards update --assignee <column id> on a card assigned to DHH exit 0, DHH removed exit 1, says it now has no assignees
cards step create, todos create/update, subtasks create, assign (to-do, card) exit 0, dropped exit 1

bc3 filters every assignee write through recording.reachable_people(include_admissible_people: true): project members, clients when the recording is visible to them, agents, and any admissible employee on an all-access project. Ids outside that set are ignored, not rejected.

The fix

Each write compares the IDs it sent with the assignees on the record Basecamp returns. Missing ones fail with a usage error that names the record, the dropped IDs and what it was saved with, and a hint that assigns that record (so an agent doesn't create a second one):

card #1069479860 was saved, but Basecamp did not assign 822400739: not a person who can be assigned there; it has no assignees
hint: Assign it with basecamp assign 1069479860 --card --to <person>, naming someone from basecamp people list --project <project>

Why check after the write rather than validate before it: no client-side lookup can reproduce bc3's rule. /people.json passes a person who isn't on the project (measured: dropped), and the project's people list would reject admissible people on an all-access project that bc3 accepts. The response is the authority, and reading it costs no extra request. The record exists by the time we fail; that matches the existing card %d created but assignment failed path in cards create.

Covered: cards create, cards update, cards step create/update, todos create/update, subtasks create/update, assign (to-do, --card, --step). unassign can't drop an ID and is untouched; the TUI picks from a people list and is out of scope. Name and email resolution is unchanged.

The second commit covers a mixed assign A B batch, where the person is kept on A and dropped on B. Partial batches still exit 0, which is the tested contract, but --agent prints only data, so B's failure was invisible. The batch now carries a diagnostic naming each failed item and why; quiet/agent mode writes it to stderr.

cards create --to stays as is. Its help now reads "Person to assign (alias for --assignee); the column is --column", since cards move --to is a column.

Verification

  • TestAssigneeWritesFailWhenBasecampDropsTheAssignee drives all 11 write paths against a mock that drops the ID; all 11 failed before the fix. TestAssigneeWritesSucceedWhenBasecampKeepsTheAssignee is the paired control. Three existing fixtures answered writes without the assignees they were sent; they now echo them, as bc3 does.
  • Live against local bc3 (verify-cli runs 20261027-235034-assignee-dropped before/after, 20261027-235248-assignee-kept for all 11 paths with valid people, 20261027-235440-assign-batch-mixed). Created records trashed.
  • bin/ci green.

Summary by cubic

Commands that assign people now fail when Basecamp silently drops a requested assignee, instead of exiting 0 with an unassigned or de-assigned record.

  • Bad IDs such as a column ID passed to cards create --to, or a person not on the project, now produce a usage error naming the record, the dropped IDs, and what the record was saved with.
  • cards update/todos update previously replaced the assignee set, so a bad ID also cleared the real assignees; that is now caught too.
  • The check compares the requested IDs against the assignees on the record Basecamp returns, so no extra request and no client-side lookup that could diverge from bc3's assignment rules.
  • Covered across cards create/update, cards step create/update, todos create/update, subtasks create/update, and assign (to-do, card, step).
  • Partial assign batches still exit 0, but under --agent/--quiet they now write a diagnostic to stderr naming each item that dropped the assignee.
  • The recovery hint keeps the assignees the record saved (subtasks update replaces the set) and names the --project flag, which assign requires.

Written for commit 614dec9. Summary will update on new commits.

View guided diff Turn on auto-fix

Basecamp ignores an assignee_ids entry it cannot assign on the
recording, such as a card column's ID or a person outside the project,
and saves the rest. `cards create --to <column id>` therefore exited 0
with an unassigned card, and `cards update --assignee <bad id>` cleared
the card's real assignees just as quietly.

Every command that sets assignees now compares what it asked for with
the assignees on the record Basecamp returns, and fails with a usage
error naming the record, the dropped IDs and what the record was saved
with. The check needs no extra request, and it uses the server's own
answer, which no client-side person lookup could reproduce: bc3 filters
through reachable_people(include_admissible_people: true).

cards create's --to help now says it is the person to assign, and that
the column is --column.
`assign A B --to <person>` exits 0 when it assigns some of the items,
by design, and --agent prints only the data. An item Basecamp dropped
the person from, or any other item that failed, was therefore
invisible there. The batch now carries a diagnostic naming each failed
item and why, which quiet mode writes to stderr.
Copilot AI balanced review requested due to automatic review settings October 28, 2026 03:59
@robzolkos
robzolkos requested a review from a team as a code owner October 28, 2026 03:59
@github-actions github-actions Bot added commands CLI command implementations tests Tests (unit and e2e) labels Oct 28, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Recovery hints omit the required project and can overwrite successfully saved subtask assignees, causing recovery to fail or remove existing assignments.

2 open issues
What changed in this PR

This PR checks saved assignees against requested IDs so CLI commands report when Basecamp silently drops an assignment, without additional API requests.

Changes:

  • Add post-write checks across 11 assignment paths for to-dos, cards, steps, and subtasks.
  • Surface partial-batch failures in quiet/agent output and clarify cards create --to help.
  • Add dropped/retained-assignee tests and update mock responses to echo assignments.

[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

File Description
internal/​commands/​todos.go Check assignees after creating or updating to-dos.
internal/​commands/​subtasks.go Check assignees after creating or updating subtasks.
internal/​commands/​subtasks_test.go Echo saved assignees in the creation fixture.
internal/​commands/​cards.go Check card and step assignments; clarify assignee help.
internal/​commands/​cards_test.go Echo assignees in step-update responses.
internal/​commands/​assignees_saved_test.go Test dropped and retained assignments, batch diagnostics, and error details.
internal/​commands/​assign.go Add shared validation, recovery hints, and batch diagnostics.
internal/​commands/​assign_test.go Echo assignees in step-assignment responses.

Review effort: Balanced

Comment thread internal/commands/assign.go Resolved
Comment thread internal/commands/assign.go Resolved
Following the subtask hint would have replaced the assignees the
subtask kept, since subtasks update replaces the whole set, so the hint
now lists them before <person>. The assign hints name --project, which
assign needs when no default project is configured.
Copilot AI balanced review requested due to automatic review settings October 28, 2026 04:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approved

No active review issues are reported for the assignee-validation and partial-batch reporting changes.

No open issues 🎉

2 resolved since last review

Review effort: Balanced

@robzolkos
robzolkos merged commit d12e9f3 into main Oct 28, 2026
32 checks passed
@robzolkos
robzolkos deleted the assignee-not-silent branch October 28, 2026 05:13
robzolkos added a commit that referenced this pull request Oct 28, 2026
* origin/main:
  Fail when Basecamp drops a requested assignee (#981)
  Point the skill at config show for project context (#980)
  Emit "data": [] for a list that found nothing (#979)
  Delete Campfire lines through their Campfire, and explain Ping refusals (#970)
@robzolkos robzolkos mentioned this pull request Oct 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commands CLI command implementations tests Tests (unit and e2e)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants