Sponsored Content
Skip to content

"linux: clarify pids cgroup settings" is causing widespread difficulty in adopting runtime-spec 1.3.0 #1305

Open

Description

@samuelkarp

(Note: this issue is a personal perspective and does not represent a stance of the OCI TOB)

869b2d5 changed LinuxPid from int64 to *int64. While the description in the commit is logical, the effect is that this creates a cascade across ecosystem packages that depend on the runtime-spec's Go types. Here is a sampling of issues:

It may be too late to unwind the change in 1.3.0 (and revert in 1.3.1?), but this was certainly a breaking change from a Go library perspective even if it wasn't from an actual spec perspective. Since this repo serves as both the home for the spec (and versions the spec according to semver) and the Go library, it's possible to introduce breaking changes in one but not the other.

I think it would be helpful to either (a) create a policy regarding the level of acceptability for breaking changes in the Go library, or (b) separate the Go library from the spec so it can be versioned independently, or (c) discourage the use of this official Go library.

Activity

  1. utam0k commented on Nov 12, 2025

    @utam0k
    Member

    I think it's healthier to maintain compatibility with specifications and libraries separately. I prefer (a) or (b).

  2. utam0k commented on Nov 12, 2025

    @utam0k
    Member

    @opencontainers/runtime-tools-maintainers What does everyone think?

  3. cyphar commented on Nov 12, 2025

    @cyphar
    Member

    I think that having it separate would better express that SemVer only applies to our specifications, not the Go bindings.

    However, I'm not quite sure how just having a separate repo would've helped here -- would the idea be we would've tagged a new major version (to do /v2 imports) to avoid build breakages? Would that not cause a different kind of confusion (/v12 of the bindings is actually runtime-spec v1.8.0)? Or would we do something like /vN is runtime-spec v1.N.0?

    My probably-quite-controversial opinion is that build breakages which occur when you do an update are not as big of a deal as other kinds of subtle breakages that SemVer is primarily concerned with (i.e., breaking behaviour changes that still compile). The way that Go selects versions can lead to confusing breakages (updating a package could cause a different package to break and this could even be triggered by a third library changing their minimum version requirement) that I think are more problematic, but they all require you to update go.sum for the breakage to occur and it is detectable at build time.

  4. tianon commented on Nov 13, 2025

    @tianon
    Member

    I think that roughly maps to (a), but with the policy being "best effort avoiding breaking changes in Go but really no promises at all because the version is the spec version" and I think that matches the reality of how we've maintained this (it's really a shame that Go decided to be so dogmatic here and doesn't have a way for us to say "no, these often preexisting tags aren't actually semantically meaningful for Go").

    If we'd had a separate repo (or module) it would only defer the breaking change, not prevent it, and be more overhead on making sure the spec and the Go stay in sync.

  5. samuelkarp commented on Nov 13, 2025

    @samuelkarp
    MemberAuthor

    However, I'm not quite sure how just having a separate repo would've helped here

    Separate repo or separate tags; you can have multiple distinct modules in the same git repo with their own versions. But that's beside the point a bit...

    would the idea be we would've tagged a new major version (to do /v2 imports) to avoid build breakages? Would that not cause a different kind of confusion (/v12 of the bindings is actually runtime-spec v1.8.0)? Or would we do something like /vN is runtime-spec v1.N.0?

    I'm not really proposing a specific solution here. /v2 or /vN could be a possibility, though the downsides you describe around confusion are definitely real. I think the issue for this particular change is that it wasn't really even a spec change at all, it was just an incompatible Go change.

  6. AkihiroSuda commented on Nov 17, 2025

    @AkihiroSuda
    Member

    I'd say this difficulty is predicted when we merged the PR, but we should have shipped a couple of alpha/beta/RC releases to attempt to notify the impact to those who are watching the releases.

  7. marked 869b2d5 broke public API #1308 as a duplicate of this issue on Dec 22, 2025
  8. wagoodman commented on Dec 22, 2025

    @wagoodman

    If the spec can be changed in a way that is not breaking for the spec, but is for the go library (from a compiler perspective) then they really should (must?) be separate.

    Since this repo is very widely used and is being imported into even more widely used projects (just looking at containerd, which is used everywhere), I think this should be carefully considered in terms of the perceived impact to consumers. What folks are seeing is this:

    consumer app
    ├── github.com/containerd/containerd (v1)
    │   └── github.com/opencontainers/runtime-spec (old version)
    └── github.com/anchore/syft
        └── github.com/anchore/stereoscope
            └── github.com/containerd/containerd/v2
                └── github.com/opencontainers/runtime-spec (new version with breaking changes)
    

    So bumping someone's usage of Syft as a library may or may not break their application if they depend on both containerd directly and Syft as well. The breakage isn't in the consumers code — it's in a sibling dependency.

    build breakages which occur when you do an update are not as big of a deal as other kinds of subtle breakages that SemVer is primarily concerned with (i.e., breaking behaviour changes that still compile).

    Breaking changes that compile are indeed bad, but that should not imply that breaking changes that don't compile are ok.

    In Go, build breakages in minor versions are actually worse than in most ecosystems because of MVS. If any dependency in your graph bumps their minimum runtime-spec version, MVS pulls you forward. You also can't pin backwards; unlike npm/cargo where you can say "give me exactly 1.2.0", for Go this means if library X requires 1.3.0 and you require 1.2.0, you get 1.3.0.
    This leads to cases where you update library A (for a security fix), and suddenly library B (which you didn't touch) stops compiling because they both share runtime-spec and A forced an upgrade.

    This really means that option (b) is the only "correct" option. Proper semver for Go modules is critical --the entire toolchain assumes minor versions don't break APIs. Assuming that claim that the spec can bump a minor version and induce a breaking change in the implementation, there really is no choice here.

    In terms of how to version the implementation, the simplest path is to not try and sync the spec and implementation versions. Since the implementation is strictly following semver and the definitions of what is breaking are seemingly different, then that strongly implies to let the major version be different in the implementation. Something like:

    github.com/opencontainers/runtime-spec-go
    ├── v1/           # go API v1 - implements spec 1.0-1.2
    │   └── specs-go/
    └── v2/           # go API v2 - implements spec 1.3+
        └── specs-go/
    

    That way the docs can drive what versions this applies to (or even constants in each package).

    If we'd had a separate repo (or module) it would only defer the breaking change, not prevent it, and be more overhead on making sure the spec and the Go stay in sync.

    The goal isn't to prevent or enable the change, it's to allow parallel consumption. With proper /v2 versioning:

    // in containerd v1.7 (hasn't migrated yet)
    import specs "github.com/opencontainers/runtime-spec-go/specs-go"
    
    // in some-new-library (already migrated)
    import specs "github.com/opencontainers/runtime-spec-go/v2/specs-go"

    Both can coexist in the same binary.

    With /v1 and /v2 available libraries can migrate incrementally, consumers aren't forced into upgrades by transitive dependencies, and the ecosystem converges naturally over 6-12 months. This is the way to ensure that nobody is blocked.

  9. schneidergithub commented on Jan 28, 2026

    @schneidergithub

    @wagoodman 5 thumbs up for this comment "If the spec can be changed in a way that is not breaking for the spec, but is for the go library (from a compiler perspective) then they really should (must?) be separate."

    Isn't the whole point of OCI to be agnostic? It seems like the spec has to be separate, whether a repo or module.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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