You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
"linux: clarify pids cgroup settings" is causing widespread difficulty in adopting runtime-spec 1.3.0 #1305
(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.
Reacted by Lucas Otárola, Alex Goodman and Jiawei Du
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.
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.
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.
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.
Reacted by Krisztian Litkey, Toru Komatsu, Alexander Jung and Justin Chadwell
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.
@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.
Activity
utam0k commentedon Nov 12, 2025
I think it's healthier to maintain compatibility with specifications and libraries separately. I prefer (a) or (b).
utam0k commentedon Nov 12, 2025
@opencontainers/runtime-tools-maintainers What does everyone think?
cyphar commentedon Nov 12, 2025
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
/v2imports) to avoid build breakages? Would that not cause a different kind of confusion (/v12of the bindings is actually runtime-spec v1.8.0)? Or would we do something like/vNis 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.sumfor the breakage to occur and it is detectable at build time.tianon commentedon Nov 13, 2025
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.
samuelkarp commentedon Nov 13, 2025
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...
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.
AkihiroSuda commentedon Nov 17, 2025
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.
wagoodman commentedon Dec 22, 2025
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:
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.
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:
That way the docs can drive what versions this applies to (or even constants in each package).
The goal isn't to prevent or enable the change, it's to allow parallel consumption. With proper /v2 versioning:
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.
schneidergithub commentedon Jan 28, 2026
@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.