Skip to content

Stop echoing back step fields the caller never changed - #620

Merged
jeremy merged 3 commits into
mainfrom
fix/card-step-title-workaround
Aug 4, 2026
Merged

Stop echoing back step fields the caller never changed#620
jeremy merged 3 commits into
mainfrom
fix/card-step-title-workaround

Conversation

@jeremy

@jeremy jeremy commented Aug 4, 2026

Copy link
Copy Markdown
Member

bc3#12521 (4e34dc83eb, on
master) made JSON step and card updates presence-aware. steps#update now
does @recording.recordable.changing(step_update_params) and replaces
assignees only if update_assignees?, so omitting a parameter leaves it
unchanged; an explicit null clears the due date and an explicit [] still
removes everyone. The public docs say the same thing now.

That retires the title workaround at all three call sites. Each re-sent the
step's current title because the old controller rebuilt the recordable from
scratch and rejected an update without one:

Tests

TestCardsStepUpdate{Assignees,Due}OnlyCarriesTitle asserted the old
behaviour, so they're inverted rather than supplemented. getCount == 0 is what
proves the extra read is gone — asserting on the request body alone would not.
New coverage pins the no-echo contract for assign/unassign --step, including
that unassigning the last person sends an explicit empty list rather than
omitting the key.

Verified live

Against production (QA sandbox project, card created and trashed afterward):

step request sends result
assign --step assignee_ids only title and due_on survived
cards step update --due due_on only title and assignees survived
unassign --step (last one) assignee_ids: [] assignees cleared; title + due_on survived

Rows 1 and 2 are exactly the two data-loss bugs reported in #604 — both are
fixed server-side, with no client release needed. Row 3 also settles a claim I
made and then withdrew on the record: unassigning the last person does not
silently no-op. The SDK builds body := map[string]any{} and guards on
req.AssigneeIDs != nil, and the CLI's removeID returns a non-nil empty
slice, so assignee_ids: [] does reach the wire.

bin/ci is green.

Not in scope

Explicit due-date clears. The CLI can't express one — cards.go only sets
req.DueOn when --due is non-empty, so --due "" is indistinguishable from
omitting the flag. Separately, SDK v0.12.0 encodes a card's due-date clear by
omitting due_on, which post-#12521 means "leave unchanged"; that's already
fixed upstream in basecamp-sdk#647 and needs an SDK release to reach us.

Refs #604 — closing that issue is a separate step, once this merges.


Summary by cubic

Send only changed fields when updating card steps to avoid clobbering titles or due dates and remove the extra read in cards step update. Also updates SKILL.md to document presence-aware step updates (omit unchanged fields; use null/[] to clear).

Written for commit 8e5787f. Summary will update on new commits.

Review in cubic

bc3#12521 made JSON step updates presence-aware: omitting a parameter now
leaves it unchanged, an explicit null clears the due date, and an explicit
empty assignee list still removes everyone. The API docs say so, and a live
check against production confirms it.

That retires the title workaround at three call sites. All three re-sent the
step's current title because the old controller rebuilt the recordable from
scratch and rejected an update without one:

- cards step update carried a dedicated CardSteps().Get purely to fetch a
  title it would send straight back. That read is gone.
- assign --step and unassign --step already fetch the step for its assignee
  list, so no request is saved there — but they stop asserting a value the
  caller never asked to change, which is the actual bug from #604: a
  concurrent title edit would be silently reverted.

TestCardsStepUpdate{Assignees,Due}OnlyCarriesTitle asserted the old behaviour,
so they are inverted rather than supplemented — getCount == 0 is what proves
the extra read is gone, since the request body alone cannot. New coverage pins
the no-echo contract for assign/unassign --step, including that unassigning the
last person sends an explicit empty list.

Refs #604
Copilot AI balanced review requested due to automatic review settings August 4, 2026 18:22
@github-actions github-actions Bot added commands CLI command implementations tests Tests (unit and e2e) labels Aug 4, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5b395944b5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/commands/assign_test.go
Comment thread internal/commands/cards_test.go
TestAssignStepCarriesTitle and TestUnassignStepCarriesTitle asserted the
workaround this branch removes. I missed them and added a near-duplicate pair
instead; CI caught it. Invert the originals in place — they already have a
transport that covers both directions and resolves people — give that
transport's step a due_on so the no-echo assertion covers the due date too,
and drop my duplicates.
Copilot AI review requested due to automatic review settings August 4, 2026 18:34

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

The skill's todo-subtask section still told agents to include the current title
on every raw step update and warned that omitting it "may reset the step title
to Untitled". Under bc3#12521 that is exactly backwards: echoing a title you
did not mean to change is what reverts a concurrent edit — the same failure
this branch removes from the CLI's own code paths.

Todo subtasks are not a separate contract. PUT card_tables/steps/:id routes to
StepsController#update for both spellings (config/routes.rb:1158), so the
presence-aware behaviour is identical. Verified on a live todo-backed subtask:
PUT with only assignee_ids preserved title and due_on; PUT with only due_on
preserved title and assignees.

Document the clears explicitly ("due_on": null, "assignee_ids": []) and keep the
note that assignee_ids replaces the whole list.
Copilot AI review requested due to automatic review settings August 4, 2026 19:29
@github-actions github-actions Bot added the skills Agent skills label Aug 4, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@jeremy
jeremy merged commit 5760ba3 into main Aug 4, 2026
24 of 25 checks passed
@jeremy
jeremy deleted the fix/card-step-title-workaround branch August 4, 2026 20:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commands CLI command implementations skills Agent skills tests Tests (unit and e2e)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants