Add bucketId to card column color and on-hold paths#291
Conversation
The Smithy spec for SetCardColumnColor, EnableCardColumnOnHold, and
DisableCardColumnOnHold was missing the {bucketId} URI label and input
field, so the generated clients in all six SDKs built bucket-less paths
like /{accountId}/card_tables/columns/{columnId}/color.json. Basecamp's
API rejects those forms with 404 — the endpoints have always required
the /buckets/{bucketId} segment. Every consumer of these three SDK
methods has been broken since the operations were added.
Reproduced with the Go SDK / basecamp-cli:
- PUT /{account}/card_tables/columns/{column}/color.json -> 404
- PUT /{account}/buckets/{bucket}/card_tables/columns/{column}/color.json -> 200
Spec change: added @httpLabel bucketId: ProjectId to all three operation
inputs and rewrote each @http URI to /{accountId}/buckets/{bucketId}/...
to match the rest of the bucket-scoped operations. Get/Update card
column paths are NOT changed in this PR — they happen to work without
the bucket today, and changing them would be a larger breaking change.
We can revisit those for spec parity in a follow-up.
Regen pipeline: make smithy-build, make -C go generate, make url-routes,
make ts-generate(+services), make rb-generate(+services), make
swift-generate, make kt-generate-services, make py-generate.
Hand-written changes:
- go/pkg/basecamp/cards.go: SetColor / EnableOnHold / DisableOnHold
wrappers now take bucketID alongside columnID and pass it through
to the generated client (breaking change to public Go API).
- typescript/tests/services/card-columns.test.ts: stubs and call
sites updated to include bucketId.
- ruby/test/basecamp/services/card_columns_service_test.rb: stubs and
call sites updated to include bucket_id:.
Verification: make smithy-check, go-check-drift, kt-check-drift,
ts-check, rb-check, py-check, kt-check, swift-check, conformance all
green. (go-check skipped — golangci-lint not installed locally; the
underlying go test passes.)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Spec Change Impact
|
There was a problem hiding this comment.
Pull request overview
This PR fixes the Smithy spec and regenerated SDKs for three Card Column operations whose URIs were missing the required /buckets/{bucketId} segment, which previously caused generated clients to call non-existent endpoints (404s).
Changes:
- Updated
SetCardColumnColor,EnableCardColumnOnHold, andDisableCardColumnOnHoldinspec/basecamp.smithyto addbucketIdas an@httpLabeland include it in the@httpURI. - Regenerated OpenAPI artifacts and all SDK service clients to use the new bucket-scoped paths and updated method signatures.
- Updated TypeScript and Ruby tests, plus Go hand-written wrappers, to pass
bucketId.
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.
Reviewed changes
Copilot reviewed 7 out of 21 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
spec/basecamp.smithy |
Adds bucketId labels and bucket-scoped URIs for the affected operations. |
openapi.json |
Regenerated OpenAPI containing updated bucket-scoped paths. |
typescript/src/generated/openapi-stripped.json |
Regenerated TS OpenAPI input with the new bucket-scoped paths. |
typescript/src/generated/schema.d.ts |
Regenerated TS type definitions reflecting new paths/params. |
typescript/src/generated/path-mapping.ts |
Updates operation mapping keys to include bucket-scoped routes. |
typescript/src/generated/services/card-columns.ts |
Updates generated TS service methods to accept bucketId and call bucket-scoped endpoints. |
typescript/src/generated/services/index.ts |
Export ordering updated due to regeneration. |
typescript/src/generated/metadata.json |
Regenerated TS operation metadata. |
typescript/tests/services/card-columns.test.ts |
Updates test calls and MSW stubs to include /buckets/{bucketId} and pass bucketId. |
ruby/lib/basecamp/generated/services/card_columns_service.rb |
Updates generated Ruby service methods to require bucket_id and use bucket-scoped endpoints. |
ruby/lib/basecamp/generated/types.rb |
Regenerated Ruby types header timestamp. |
ruby/lib/basecamp/generated/metadata.json |
Regenerated Ruby operation metadata. |
ruby/test/basecamp/services/card_columns_service_test.rb |
Updates Ruby test stubs and call sites to pass bucket_id. |
python/src/basecamp/generated/services/card_columns.py |
Updates generated Python service signatures/paths to include bucket_id for the affected operations. |
swift/Sources/Basecamp/Generated/Services/CardColumnsService.swift |
Updates generated Swift methods to accept bucketId and use bucket-scoped endpoints. |
kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/card-columns.kt |
Updates generated Kotlin methods to accept bucketId and use bucket-scoped endpoints. |
kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/Types.kt |
Moves/retains generated Kotlin request body types due to regeneration. |
go/pkg/generated/client.gen.go |
Updates generated Go OpenAPI client interface + request builders for new bucket-scoped endpoints. |
go/pkg/basecamp/cards.go |
Updates Go hand-written CardColumnsService wrappers to accept/pass bucketID. |
go/pkg/basecamp/url-routes.json |
Regenerated route table to include bucket-scoped card column routes. |
go.work.sum |
Updates Go workspace sums due to regeneration/tooling changes. |
Comments suppressed due to low confidence (2)
spec/basecamp.smithy:4285
SetCardColumnColoris configured with@basecampRetry(... retryOn: [429, 503]), but itserrors:list omitsRateLimitError. That causes generated OpenAPI/SDK types to omit the 429 response shape even though clients may retry on 429. AddRateLimitErrorto the operation’serrors:list to keep the modeled error responses consistent with the retry contract.
operation SetCardColumnColor {
input: SetCardColumnColorInput
output: SetCardColumnColorOutput
errors: [NotFoundError, ValidationError, UnauthorizedError, ForbiddenError, InternalServerError]
}
spec/basecamp.smithy:4347
DisableCardColumnOnHoldhas@basecampRetry(... retryOn: [429, 503])but itserrors:list does not includeRateLimitError, so generated OpenAPI/SDKs won’t model a 429 response for this operation. IncludeRateLimitErrorin theerrors:list (and regenerate) so rate-limit failures are represented consistently across clients.
@http(method: "DELETE", uri: "/{accountId}/buckets/{bucketId}/card_tables/columns/{columnId}/on_hold.json")
operation DisableCardColumnOnHold {
input: DisableCardColumnOnHoldInput
output: DisableCardColumnOnHoldOutput
errors: [NotFoundError, UnauthorizedError, ForbiddenError, InternalServerError]
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Asserts method and full URL path for SetColor, EnableOnHold, and DisableOnHold. The path assertion uses distinct bucketID and columnID constants so a future swap of the wrapper's (bucketID, columnID) argument order builds a path with the IDs in the wrong slots and fails the test, rather than silently producing a 404 against the live API like the bug this PR fixes.
EnableCardColumnOnHold already declared RateLimitError in its errors list, but its two siblings — SetCardColumnColor and DisableCardColumnOnHold — did not, despite all three sharing @basecampRetry(retryOn: [429, 503]). Align the three operations. Inserts RateLimitError between ForbiddenError and InternalServerError to match the existing ordering convention. Regenerates the affected artifacts: openapi.json, the Go generated client (JSON429 fields and parse cases), and the TS schema/openapi-stripped.json. Ruby/TS metadata and Ruby types.rb also pick up regen timestamps. The hand-written wrappers in go/pkg/basecamp/cards.go are unchanged — they route errors through generic checkResponse/checkError, so the new JSON429 fields surface automatically without wrapper updates.
There was a problem hiding this comment.
Pull request overview
Fixes broken Card Column color/on-hold operations by making them bucket-scoped (adding bucketId as an HTTP label and inserting /buckets/{bucketId} into the URI), aligning generated SDK paths with the Basecamp API’s required routing.
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.
Changes:
- Update Smithy spec for
SetCardColumnColor,EnableCardColumnOnHold, andDisableCardColumnOnHoldto requirebucketIdin the path. - Regenerate OpenAPI + generated SDK surfaces (TS/Ruby/Python/Swift/Kotlin/Go) so client methods include
bucketIdand build correct URIs. - Update/extend tests (TypeScript, Ruby, Go) to assert the new bucket-scoped request paths.
Reviewed changes
Copilot reviewed 8 out of 22 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| typescript/tests/services/card-columns.test.ts | Updates MSW stubs and call sites to include bucketId in paths and method signatures. |
| typescript/src/generated/services/index.ts | Reorders exports (no functional change). |
| typescript/src/generated/services/card-columns.ts | Updates generated service methods to use bucket-scoped routes and signatures. |
| typescript/src/generated/schema.d.ts | Regenerates OpenAPI TypeScript types reflecting new bucket-scoped paths/params. |
| typescript/src/generated/path-mapping.ts | Updates operationId mapping for new bucket-scoped Card Column endpoints. |
| typescript/src/generated/openapi-stripped.json | Regenerates stripped OpenAPI with new paths for these operations. |
| typescript/src/generated/metadata.json | Regenerates per-operation metadata (retry/idempotency/etc.) after spec change. |
| swift/Sources/Basecamp/Generated/Services/CardColumnsService.swift | Updates generated Swift service signatures and paths to include bucketId. |
| spec/basecamp.smithy | Adds bucketId to inputs and fixes the three @http URIs to include /buckets/{bucketId}. |
| ruby/test/basecamp/services/card_columns_service_test.rb | Updates stubs + call sites to pass bucket_id and assert new URIs. |
| ruby/lib/basecamp/generated/types.rb | Updates generation timestamp (regen artifact). |
| ruby/lib/basecamp/generated/services/card_columns_service.rb | Updates generated Ruby service paths/signatures to include bucket_id. |
| ruby/lib/basecamp/generated/metadata.json | Updates generation timestamp and operation metadata (regen artifact). |
| python/src/basecamp/generated/services/card_columns.py | Updates generated Python service method signatures/paths to include bucket_id. |
| kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/card-columns.kt | Updates generated Kotlin service signatures/paths to include bucketId. |
| kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/Types.kt | Moves/updates generated request body type placement (regen artifact). |
| go/pkg/generated/client.gen.go | Updates generated Go client interfaces/builders to include bucketId path label. |
| go/pkg/basecamp/url-routes.json | Updates generated URL route table to include bucket-scoped Card Column endpoints. |
| go/pkg/basecamp/cards_test.go | Adds Go tests asserting correct bucket-scoped paths for the wrapper methods. |
| go/pkg/basecamp/cards.go | Updates Go wrapper method signatures to take bucketID and pass through to generated client. |
| openapi.json | Regenerates OpenAPI with corrected bucket-scoped paths for these operations. |
| go.work.sum | Updates Go workspace sums (regen/tidy artifact). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
jeremy
left a comment
There was a problem hiding this comment.
Approve. Spec fix is correct: cross-checked against the BC3 Rails app — those controllers are BucketScoped and the routes live under resources :buckets, so /buckets/{bucketId}/... is the right target. Some published API doc examples show the bucket-less form, but those are stale; the Rails routes are the source of truth and reject the bucket-less form with 404 (reproduced).
Hand-written cards.go wrapper signature changes (SetColor, EnableOnHold, DisableOnHold) and the TS/Ruby test stub updates are correct. Regen is consistent across all six SDKs. The go.work.sum diff isn't unrelated churn — running go build ./... from the parent reproducibly re-adds the same 5 lines, so this PR is correcting pre-existing workspace-sum drift on main. @cubic found no issues on the original commit.
Added two fixup commits onto the branch:
-
9c15b67Add CardColumns httptest coverage for bucket-scoped paths. TS and Ruby already assert URL paths in their test stub diffs; Go didn't. Adds threehttptest-backed tests pinning method and full path forSetColor/EnableOnHold/DisableOnHoldusing distinctbucketIDandcolumnIDconstants — a future swap of the wrapper's(bucketID, columnID)argument order builds the path with the IDs in the wrong slots and fails the test, rather than silently producing a 404 against the live API like the bug this PR fixes. -
58c00e3Model RateLimitError on retry-marked card column ops. Picks up @copilot's two flags.EnableCardColumnOnHoldalready declaredRateLimitError; aligningSetCardColumnColorandDisableCardColumnOnHoldso all three siblings (which share@basecampRetry(retryOn: [429, 503])) model the 429 response consistently. Spec edit + clean regen across the affected artifacts. The wider asymmetry (203 retry-marked ops vs 97 modelingRateLimitError) is a separate cleanup PR.
CI is green on head 58c00e3.
One follow-up worth filing separately: grep for these three operations in conformance/tests/*.json returns nothing, which is exactly how a wrong-path bug reaches the live API undetected. Adding conformance fixtures (templated on the recent uploads-download work in #280/#281) would close the gap for this class of regression.
Summary
Three card column operations had bucket-less URIs in
spec/basecamp.smithy, so the generated clients in all six SDKs built paths like/{accountId}/card_tables/columns/{columnId}/color.json. Basecamp's API rejects those with 404 — the endpoints have always required/buckets/{bucketId}in the path. Every consumer ofSetCardColumnColor,EnableCardColumnOnHold, andDisableCardColumnOnHoldhas been broken since these operations were added.Reported via Basecamp card 9825845889 (Helpscout case from a Jack Henry customer). The basecamp-cli has a stopgap workaround at basecamp/basecamp-cli#455 — once this PR ships and CLI bumps to the new SDK, that workaround will be reverted.
Reproduction (verified live)
PUT /3642500/card_tables/columns/7103332776/color.json→ 404 Not FoundPUT /3642500/buckets/36395297/card_tables/columns/7103332776/color.json→ 200 OKSpec change
Added
@httpLabel bucketId: ProjectIdto all three operation inputs and rewrote each@httpURI to include/buckets/{bucketId}/, matching the pattern used by other bucket-scoped operations (ListWebhooks, etc.).GetCardColumnandUpdateCardColumnpaths are not changed in this PR. Those happen to work without the bucket today (Basecamp's API tolerates bucket-less reads on these specific endpoints), and changing them would be a larger breaking change with no functional benefit. Worth a follow-up to re-align them with the spec for consistency.Breaking change
Go SDK:
SetColor,EnableOnHold,DisableOnHoldnow take abucketID int64parameter beforecolumnID. TypeScript / Ruby / Python / Swift / Kotlin generated method signatures gain abucketIdparameter. Since the previous signatures couldn't ever produce a successful API call, no currently-working caller is affected.Regeneration
Ran the full pipeline:
make smithy-build(Smithy → openapi.json)make -C go generate && make url-routesmake ts-generate ts-generate-servicesmake rb-generate rb-generate-servicesmake swift-generatemake kt-generate-servicesmake py-generateHand-written updates:
go/pkg/basecamp/cards.go—SetColor/EnableOnHold/DisableOnHoldwrappers takebucketIDand pass it through.typescript/tests/services/card-columns.test.ts— call sites and MSW stubs updated.ruby/test/basecamp/services/card_columns_service_test.rb— call sites and WebMock stubs updated.Test plan
make smithy-check— spec round-trips cleanly.make go-check-drift/kt-check-drift— no service-layer drift.make ts-check— 616 tests passed.make rb-check— 599 runs / 905 assertions passed; rubocop clean.make py-check— 209 tests passed; mypy + ruff clean.make kt-check/swift-check— pass.make conformance— 64 passed, 0 failed.cd go && make test— pass. (make go-checkskipped: golangci-lint not installed locally; CI will exercise it.)Follow-ups (not blocking this PR)
cards column color404 from missing bucket in URL basecamp-cli#455 after this ships and the CLI bumps the SDK.GetCardColumnandUpdateCardColumnpaths to be bucket-scoped for spec consistency.🤖 Generated with Claude Code