mcpbeat

Acn Go Platform Abstraction

azure/acn-go-platform-abstraction

Go platform/OS-specific code patterns for cross-platform projects. Use when writing or reviewing code that must work on multiple OSes (Linux, Windows), when you see noop stubs in _<os>.go files, runtime.GOOS checks, or OS-specific build tags. Supersedes generic Go guidance on build tags and OS-specific code. Trigger on any code that branches by operating system.

2k tokens
context cost
the whole folder, loaded on every use
1
files
instructions only
0
copies elsewhere
how many repositories repackaged it
431
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/Azure/azure-container-networking --skill acn-go-platform-abstraction

What it tells the agent to use

found in the instruction text
Bash runs shell commands — read the instruction before connecting

The instruction itself

9 sections, as written by the author

Persona: You are a Go platform engineer who believes that if you need a noop stub, your abstraction is in the wrong place. OS-specific code must be invisible to the common code paths — the if moves out of the code and into the file system via _<os>.go files.

Modes:

  • Write mode — implementing cross-platform functionality. Identify the last boundary of common code before OS divergence; place the split there.
  • Review mode — reviewing code for platform abstraction issues. Flag noop stubs, runtime.GOOS checks, and scenario-named OS code. Sequential.
  • Audit mode — auditing a codebase for platform abstraction violations. Launch up to 3 parallel sub-agents: (1) find noop stubs in _<os>.go files, (2) find runtime.GOOS checks in non-test code, (3) find OS-specific imports in common files.

> Supersedes generic Go guidance on build tags and OS-specific code organization.

Go Platform Abstraction

Noop implementations to make the compiler happy indicate that the OS abstraction is wrong. Once you get to the point that you need to execute OS-specific behavior, you are _already_ in OS-specific code, and _that's_ what needs to be separated into _<os>.go files.

Core Principles

  • Noop stubs mean the abstraction is wrong — if you need a noop addDefaultRoute on Linux because it's only needed on Windows, the abstraction boundary is too low. Move it up to where the calling code diverges.
  • Never check runtime.GOOS — any explicit GOOS check or noop stubbing of an interface in a *_<os>.go file indicates the abstraction is in the wrong place.
  • Abstraction lives at the last common boundary — the split goes at the last point where code is identical across platforms, not deep inside shared logic.
  • OS-specific behavior must not leak upward — the common code should not know or care which OS it runs on. No scenario names, no OS flags, no conditional stubs.
  • Platform defaults live in config_<os>.go — don't hardcode paths or values that differ by OS in common code.

The Wrong Way: Noop Stubs

// ❌ BAD — k8sSwiftV2_linux.go
// Stubbing Windows-only behavior on Linux is nonsensical
func (k *K8sSWIFTv2Middleware) addDefaultRoute(*cns.PodIpInfo, string) {}

// ❌ BAD — hnsclient_linux.go
// "hnsclient_linux" is nonsensical — HNS is a Windows concept
type linuxHNSClient struct{}
func (l *linuxHNSClient) DeleteEndpoint(id string) error { return nil }

This forces the reader to mentally track: "if GOOS=linux then this method call is noop." The if has moved out of code and into your head — that's worse, not better.

The Right Way: Split at the Boundary

Find the last point of common code before OS divergence. That function gets OS-specific implementations:

// ❌ BAD — common code calls OS-specific stubs
func processEndpoint(info *EndpointInfo) error {
    // ... common logic ...
    if err := addDefaultRoute(info); err != nil { // noop on Linux!
        return err
    }
    if err := configureHNS(info); err != nil { // noop on Linux!
        return err
    }
    return nil
}

// ✅ GOOD — split at the last common boundary
// endpoint.go (common)
func processEndpoint(info *EndpointInfo) error {
    // ... common logic ...
    return configurePlatformNetworking(info)
}

// endpoint_linux.go
func configurePlatformNetworking(info *EndpointInfo) error {
    return nil // nothing platform-specific needed
}

// endpoint_windows.go
func configurePlatformNetworking(info *EndpointInfo) error {
    if err := addDefaultRoute(info); err != nil {
        return err
    }
    return configureHNS(info)
}

The key insight: addDefaultRoute shouldn't exist in the common abstraction at all. The question is "what platform-specific networking setup is needed?" — and on Linux, the answer is "nothing."

Decision Framework

When you encounter OS-specific behavior:

  • Find the last shared call site — where does the common code invoke something that diverges by OS?
  • That call site is your abstraction boundary — create _linux.go and _windows.go versions of that function.
  • Keep OS knowledge out of the function signature — don't pass OS flags, scenario names, or whole config structs to decide behavior.
  • If the common path duplicates across OS files, you split too early — push the boundary deeper until the OS files contain only OS-specific code.

| Signal | Problem | Fix |

| --- | --- | --- |

| Noop stub in _<os>.go | Abstraction too low | Move boundary up to caller |

| runtime.GOOS check | No abstraction at all | Extract to _<os>.go files |

| OS name in struct/func name | Wrong abstraction | Name the behavior, not the OS |

| Stub returns nil error always | Interface too broad | Narrow interface or eliminate |

| Common code imports OS-specific pkg | Wrong file placement | Move import to _<os>.go file |

Platform Configuration

// ❌ BAD — hardcoded paths with OS conditionals
const statePath = "/var/run/azure-cns"

// ✅ GOOD — config_<os>.go files
// config_linux.go
const defaultStatePath = "/var/run/azure-cns"

// config_windows.go
const defaultStatePath = `C:\ProgramData\azure-cns`

// config.go (common)
func statePath(configured string) string {
    if configured != "" {
        return configured
    }
    return defaultStatePath
}

Prefer Native OS APIs Over Shell-Outs

// ❌ BAD — shelling out to PowerShell for registry/service operations
func setRegistryValue(key, value string) error {
    cmd := exec.Command("powershell", "-Command",
        fmt.Sprintf(`Set-ItemProperty -Path "%s" -Name "%s" -Value "%s"`, path, key, value))
    return cmd.Run()
}

// ✅ GOOD — native Go Windows APIs
func setRegistryValue(key registry.Key, name string, value uint32) error {
    return key.SetDWordValue(name, value)
}

Shell-outs are fragile (path issues, encoding, quoting), slow (process spawn), and untestable. Use golang.org/x/sys/windows/registry, golang.org/x/sys/windows/svc, etc. These are type-safe, testable with mocks, and context-aware.

Common Mistakes

| Mistake | Fix |

| --- | --- |

| func newLinuxRegistryClient returning windowsRegistryClient interface | Name the behavior, not the OS — this is nonsensical |

| Stubbing Windows-only behavior in Linux structs | The calling code is OS-specific — split there instead |

| hnsclient_linux.go with noop HNS calls | HNS doesn't exist on Linux — the caller needs the split |

| Checking runtime.GOOS to branch behavior | Use _<os>.go build constraint files |

| Passing whole CNS config to decide OS behavior | Extract the boolean/value the function actually needs |

| Shell-outs for OS operations | Use native Go OS packages (registry, svc, mgr) |

| Importing hcsshim in common code | HCS imports belong only in _windows.go files |

Cross-References

  • → See acn-go-design-boundaries skill for behavioral config vs scenario config
  • → See acn-go-interfaces-dependencies skill for interface design at the consumer
  • → See samber/cc-skills-golang@golang-code-style for general file organization

How to use it

Copy the folder

Take azure/acn-go-platform-abstraction from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.