Skip to content

WorkspaceUpdateOptions: VCSRepo without omitempty causes destructive partial updates #212

Description

@jutley

Summary

WorkspaceUpdateOptions.VCSRepo is tagged jsonapi:"attr,vcs-repo" without omitempty. This means any call to Workspaces.Update() that doesn't explicitly set VCSRepo sends "vcs-repo": null in the request payload, which Scalr interprets as "delete the VCS repository configuration." This silently strips VCS settings (trigger patterns, branch, working directory) from the workspace.

The same issue affects Terragrunt in the same struct.

Reproduction

// Intending to only change execution mode:
client.Workspaces.Update(ctx, wsID, scalr.WorkspaceUpdateOptions{
    ExecutionMode: scalr.WorkspaceExecutionModePtr(scalr.WorkspaceExecutionModeLocal),
})
// Result: execution mode changes, but VCS repo is also deleted.

Expected behavior

Setting only ExecutionMode should not affect VCSRepo. The Scalr API supports partial updates — absent fields are treated as "don't change." The scalr CLI correctly implements this by only including fields the user explicitly passed.

Root cause

The svanharmelen/jsonapi serializer uses omitempty as its sole mechanism to distinguish "don't send this field" from "send null." Every other pointer field in WorkspaceUpdateOptions has omitempty and works correctly for partial updates. VCSRepo and Terragrunt omit it so that nil can express "delete this resource," but this makes it impossible to leave those fields untouched.

// These fields work correctly for partial updates (omitempty present):
Operations    *bool                   `jsonapi:"attr,operations,omitempty"`
ExecutionMode *WorkspaceExecutionMode `jsonapi:"attr,execution-mode,omitempty"`

// These fields are destructive on every update (omitempty absent):
VCSRepo    *WorkspaceVCSRepoOptions    `jsonapi:"attr,vcs-repo"`
Terragrunt *WorkspaceTerragruntOptions `jsonapi:"attr,terragrunt"`

Possible fixes

  1. Tri-state wrapper type that distinguishes Unset / Null / Value, allowing omitempty to be restored while still supporting explicit deletion.
  2. Separate boolean fields like ClearVCSRepo *bool for the delete operation, with omitempty added back to VCSRepo.
  3. Custom MarshalJSONAPI implementation on WorkspaceUpdateOptions that skips nil fields regardless of tag.

Workaround

We work around this by sending a raw HTTP PATCH with a hand-built JSON:API payload containing only the field we want to change, bypassing the SDK entirely.

Affected version

v0.0.0-20251128082633-2ff36e0bea2a

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions