Repository navigation
Conversation
…rd table name two tables share A 5xx or dropped connection on the copy request can follow a copy Basecamp already started, so the error is no longer retryable and says how to check. A rate limit stays retryable: Basecamp refuses it before copying. A --to-card-table name that matches two tables now lists both instead of taking the first.
…mit gate output.AsError renders a gate error found anywhere in the cause chain in place of the error around it, so a gate trip while polling turned "do not copy it again" into a retryable rate limit. The in-progress and read-back errors now keep their cause as text.
Reading a moved record's old id redirects to the record at its new id, but cards show, cards steps and todos show kept using the old id for the full step list, comments and follow-up commands, which then 404.
There was a problem hiding this comment.
🟡 Changes recommended
URL validation and explicit source-project checks remain incomplete, and uncertain copy failures can omit duplicate-copy warnings.
4 open issues
What changed in this PR
Adds cross-project card moves and copies to the CLI, including background-operation tracking and support for reading moved records through their old IDs.
Changes:
- Adds
cards copyand destination flags for card moves, with optional comment copying. - Reports landed cards after moves and follows updated IDs when reading cards, to-dos and steps.
- Updates the SDK dependency, MCP catalog, documentation and tests. The SDK release and repinning remain prerequisites for merging.
[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or rungh pr ready --undo.
Click "Ready for review" or rungh pr readyto reengage.
| File | Description |
|---|---|
nix/package.nix |
Updates the dependency vendor hash. |
internal/version/sdk-provenance.json |
Records the pinned SDK revision. |
internal/output/codes.go |
Adds the background-work status code. |
internal/mcpserver/testdata/catalog_snapshot.txt |
Lists three new recording actions. |
internal/mcpserver/model/PROVENANCE.json |
Updates model source provenance. |
internal/mcpserver/model/openapi.json |
Adds move, copy and filing schemas. |
internal/mcpserver/model/behavior-model.json |
Adds operation behavior metadata. |
internal/mcpserver/catalog_test.go |
Updates the served operation count. |
internal/commands/todos.go |
Uses moved to-dos’ new IDs. |
internal/commands/subtasks_test.go |
Covers step reads after record moves. |
internal/commands/commands.go |
Registers the copy action in the catalog. |
internal/commands/cards.go |
Implements card filing, polling and destination resolution. |
internal/commands/cards_test.go |
Tests landed and pending wormhole moves. |
internal/commands/cards_filing_test.go |
Covers filing destinations, failures and polling. |
go.sum |
Updates SDK checksums. |
go.mod |
Pins the SDK commit providing filing APIs. |
e2e/smoke/smoke_cards_write.bats |
Adds a card-copy smoke test. |
API-COVERAGE.md |
Documents filing endpoint coverage. |
.surface |
Records the new command and flags. |
Review effort: Balanced
…ther project cards move --to-project/--to-card-table and cards copy took the card id and ignored an explicit --in, --project or card URL bucket, so a card in project 123 moved even when the caller said 888. They now check those against the card's own project before filing it, as the wormhole move already did. The check is renamed rejectCardProjectConflict and its hint no longer names wormholes. Config and interactive defaults are not constraints.
…ss-table moves and copies cards copy and cards move --to-project/--to-card-table reduced a pasted URL to its trailing number, so a look-alike host, another account's card, a to-do or a cards listing filed whichever card in this account had that number. The URL must now be a single card on a trusted Basecamp host in the session's account, checked before any request, as files replace and boost update do.
…o-project A destination card table URL was reduced to its number without checking the host or account, and its project was ignored when --to-project named another. It is now held to the same checks as the card URL, a project that disagrees with --to-project is refused, and the destination is resolved before the card is read so a bad URL is refused before any request.
A dropped connection or an unreadable answer to the copy request came back as a plain error, which skipped the "may have started the copy" warning because only retryable errors got it. The rule is inverted: every failed copy request gets the warning and is not retryable, except a 4xx answer, which shows Basecamp refused it, and a rate limit, which stays retryable. Moves are unchanged.
A moved message's old id redirects through the card endpoint to the message, so the read alone does not prove the id names a card.
|
Review summary for
|
v0.30.0 is the release of the move, copy and filing operations this branch used from a pre-release pin. It also brings project logos: the built-in MCP server serves remove_project_logo and skips the multipart UpdateProjectLogo, as it skips the other uploads.
|
Repinned to the released basecamp-sdk v0.30.0 ( |


Why
On the web, a card's Move… and Copy… menus send it to a card table in any project. The CLI could only cross projects through a wormhole someone had already set up. It couldn't copy a card, and after a wormhole move it never said where the card landed. CLI users miss all three.
What changed
cards move <card> --to-project <project>(or--to-card-table) moves the card to that project's card table.cards copy <card>copies it onto its own table or, with--to-project, another project's, and--include-commentsbrings the comments. Both wait for Basecamp to finish and print the new card. As on the web, the card lands in Triage, or in Done if it's completed.--to-wormholenow prints the card that landed, and--towith another project's column points to--to-project.cards show,cards stepsandtodos showfollow a moved card or to-do from its old id to its new one.Scope
remove_project_logoand skips the upload, as it skips the other uploads. CLI logo commands come in a separate PR stacked on this one.move_recording,copy_recordingandget_filingto the built-in MCP server's catalog.Tradeoffs
A copy is never retried. A copy that fails to start with a 5xx, or that's still running when the wait ends, says "don't copy again" and is not marked retryable, because Basecamp may already be making it.
Verification
bin/cipasses.--include-commentscarried a comment and a completed card landed in Done.Summary by cubic
Adds cross-project card moves and copies to the CLI, matching the web's Move… and Copy… menus.
cards move <card> --to-project <project>(or--to-card-table) files the card into that project's card table; it lands in Triage, or Done if completed, under a new id, with a bounded backoff wait on Basecamp's background move.cards copy <card>copies onto its own table or, with--to-project, another project's;--include-commentscarries the comments. A copy that fails is never retried unless Basecamp answered 4xx, because a 5xx, dropped connection, or wait time-out may already have started it; rate limits stay retryable.--to-wormholenow prints where the card landed, andcards show,cards steps, andtodos showfollow a moved record from its old id to its new one;--toa foreign column suggests--to-project.--in/--projectmust match the card's own project, and a destination table URL whose project disagrees with--to-projectis refused. A record that reads back as any type other than a card is refused.Scope
Bumps
basecamp-sdkto v0.30.0, the release of the move, copy, and filing operations. It also brings project logos: the built-in MCP server servesremove_project_logoand skips the multipart upload.Written for commit 539bc14. Summary will update on new commits.