From aaa4e4486134363cfeba3469be5c4da27f48f4b6 Mon Sep 17 00:00:00 2001 From: Jeffrey Hardy Date: Tue, 28 Apr 2026 14:19:20 -0400 Subject: [PATCH] Fix `cards column color` bucket-less URL 404 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `basecamp cards column color` was sending PUT requests without the `/buckets/{id}` path segment, causing every invocation to 404. The SDK's generated client (basecamp-sdk/go v0.7.3) builds the path `/{accountId}/card_tables/columns/{columnId}/color.json` — missing the bucket. Basecamp tolerates this for `Get`/`Update` but rejects it for `/color.json`, so the command was unusable. Workaround in the CLI until the SDK is fixed: resolve the bucket from the URL, `--in`/`--project` flag, or config (the same pattern as `cards column show` and others), then send the PUT via the raw API helper `app.Account().Put` to the correct bucket-scoped path. Re-fetch through the SDK `Get` so the typed `*CardColumn` return shape is preserved. Same SDK bug affects `cards column on-hold` and `cards column no-on-hold` (`/on_hold.json`); those are not addressed here. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/commands/cards.go | 39 ++++++++++++++++++++++++++++----- internal/commands/cards_test.go | 3 ++- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/internal/commands/cards.go b/internal/commands/cards.go index 49f52e4a..fdcd0cb9 100644 --- a/internal/commands/cards.go +++ b/internal/commands/cards.go @@ -1271,7 +1271,7 @@ func newCardsColumnCmd(project, cardTable *string) *cobra.Command { newCardsColumnUnwatchCmd(), newCardsColumnOnHoldCmd(), newCardsColumnNoOnHoldCmd(), - newCardsColumnColorCmd(), + newCardsColumnColorCmd(project), ) return cmd @@ -1726,7 +1726,7 @@ You can pass either a column ID or a Basecamp URL: return cmd } -func newCardsColumnColorCmd() *cobra.Command { +func newCardsColumnColorCmd(project *string) *cobra.Command { var color string cmd := &cobra.Command{ @@ -1750,14 +1750,43 @@ You can pass either a column ID or a Basecamp URL: return err } - // Extract ID from URL if provided - columnIDStr := extractID(args[0]) + // Extract ID and project from URL if provided + columnIDStr, urlProjectID := extractWithProject(args[0]) columnID, err := strconv.ParseInt(columnIDStr, 10, 64) if err != nil { return output.ErrUsage("Invalid column ID") } - col, err := app.Account().CardColumns().SetColor(cmd.Context(), columnID, color) + // Resolve project - use URL > flag > config, with interactive fallback. + // The color endpoint requires a bucket-scoped path (PUT /buckets//card_tables/columns//color.json). + projectID := *project + if projectID == "" && urlProjectID != "" { + projectID = urlProjectID + } + if projectID == "" { + projectID = app.Flags.Project + } + if projectID == "" { + projectID = app.Config.ProjectID + } + if projectID == "" { + if err := ensureProject(cmd, app); err != nil { + return err + } + projectID = app.Config.ProjectID + } + + resolvedProjectID, _, err := app.Names.ResolveProject(cmd.Context(), projectID) + if err != nil { + return err + } + + path := fmt.Sprintf("/buckets/%s/card_tables/columns/%d/color.json", resolvedProjectID, columnID) + if _, err := app.Account().Put(cmd.Context(), path, map[string]string{"color": color}); err != nil { + return convertSDKError(err) + } + + col, err := app.Account().CardColumns().Get(cmd.Context(), columnID) if err != nil { return convertSDKError(err) } diff --git a/internal/commands/cards_test.go b/internal/commands/cards_test.go index e1e621b0..49582e19 100644 --- a/internal/commands/cards_test.go +++ b/internal/commands/cards_test.go @@ -127,7 +127,8 @@ func TestCardsColumnColorShowsHelp(t *testing.T) { // Configure app with project app.Config.ProjectID = "123" - cmd := newCardsColumnColorCmd() + project := "" + cmd := newCardsColumnColorCmd(&project) err := executeCommand(cmd, app, "456") // column ID but no --color assert.NoError(t, err, "expected help output, not error")