From a61301fb4ce90287f9ccaa5d43b2c09be2a57169 Mon Sep 17 00:00:00 2001 From: Knut Zuidema Date: Fri, 11 Sep 2026 16:42:17 +0200 Subject: [PATCH] fix(pipeline): preserve casing when normalizing org/pipeline slugs normalizeSlug lowercased org and pipeline slugs parsed from 'org/pipeline' arguments, --pipeline, and --org, causing 404s for organizations with mixed-case slugs (e.g. AcmeCorp). This affected 'pipeline view', 'pipeline copy', 'job list --pipeline', and 'artifacts list --pipeline'. Buildkite slugs may be mixed case, so only normalize underscores to hyphens and leave casing untouched. Fixes #985 --- internal/pipeline/resolver/cli.go | 11 ++++++----- internal/pipeline/resolver/cli_test.go | 6 +++--- internal/pipeline/resolver/resolver_test.go | 4 ++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/internal/pipeline/resolver/cli.go b/internal/pipeline/resolver/cli.go index 731895cd..dd880693 100644 --- a/internal/pipeline/resolver/cli.go +++ b/internal/pipeline/resolver/cli.go @@ -66,10 +66,11 @@ func parsePipelineArg(arg string, conf *config.Config) (org, pipeline string) { return normalizeSlug(org), normalizeSlug(pipeline) } -// normalizeSlug converts user input to a valid Buildkite slug. Slugs may only contain -// lowercase letters, numbers and dashes, so this is a no-op on anything already valid. -// It allows passing a name like "my_pipeline" (eg. from a repository directory name) -// and matching the "my-pipeline" slug Buildkite derives from it. +// normalizeSlug converts user input to a valid Buildkite slug. Slugs may contain +// letters (of any case), numbers and dashes, so this is a no-op on anything already +// valid. It allows passing a name like "my_pipeline" (eg. from a repository directory +// name) and matching the "my-pipeline" slug Buildkite derives from it. Casing is +// preserved since Buildkite organization and pipeline slugs may be mixed case. func normalizeSlug(s string) string { - return strings.ToLower(strings.ReplaceAll(s, "_", "-")) + return strings.ReplaceAll(s, "_", "-") } diff --git a/internal/pipeline/resolver/cli_test.go b/internal/pipeline/resolver/cli_test.go index 728a48f6..a2dd4294 100644 --- a/internal/pipeline/resolver/cli_test.go +++ b/internal/pipeline/resolver/cli_test.go @@ -35,10 +35,10 @@ func TestParsePipelineArg(t *testing.T) { org: "testing", pipeline: "shared-gem", }, - "uppercase_normalized_to_lowercase": { + "casing_preserved": { url: "My_Org/Shared_Gem", - org: "my-org", - pipeline: "shared-gem", + org: "My-Org", + pipeline: "Shared-Gem", }, } diff --git a/internal/pipeline/resolver/resolver_test.go b/internal/pipeline/resolver/resolver_test.go index 4ad95994..fe084daf 100644 --- a/internal/pipeline/resolver/resolver_test.go +++ b/internal/pipeline/resolver/resolver_test.go @@ -88,8 +88,8 @@ func TestWithOrg(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - if p.Org != "override-org" { - t.Fatalf("expected org override-org, got %s", p.Org) + if p.Org != "Override-Org" { + t.Fatalf("expected org Override-Org, got %s", p.Org) } }) }