Skip to content

Rename endpoint changed somehow but the change doesn't appear in the version diff #7011

Closed

Description

@jsoref

Break in /rename endpoint caused check-spelling to stop working

Yes, I'm not actively using the versioned API, but unless you can show that had I used it, I wouldn't have been broken, I don't see the point.


From my perspective, versioning should tell a meaningful story. I should be able to understand before and after. I should be able to know if the thing I'm trying to use will stop working/has stopped working.

I'm open for a chat (I have a standing chat scheduled with another githubber for tomorrow...), but I don't think I can effectively characterize things in the format of https://github.com/github/rest-api-description/blob/main/.github/ISSUE_TEMPLATE/schema-inaccuracy.md.

Activity

  1. shawnHartsell commented on Aug 21, 2026

    @shawnHartsell
    Contributor

    Yes, I'm not actively using the versioned API, but unless you can show that had I used it, I wouldn't have been broken, I don't see the point.

    For clarity, there is no un-versioned mode of our public API. When clients omit the X-GitHub-Api-Version header, the version selected is our oldest supported version. Today that's 2022-11-28. We highly recommend clients explicitly supply a version header to avoid breaking changes as older versions are sunset.

    That said, version pinning would not have helped here. The change you observed was shipped around April 2026 and applied to both 2022-11-28 and 2026-03-10. Areas such as status code precedence and error message bodies are considered undocumented behavior and not covered by our breaking change policy. This is why you saw an empty diff between 2022-11-28 and 2026-03-10. The advertised schema for the endpoint did not change and therefore did not require a new version.

    Generally, clients should avoid relying on undocumented behavior. The authorization probe in unknown-words.sh sent an invalid request and grepped for an error message. A more deterministic check is a valid call made with a token carrying the expected scopes, guaranteed to return a 403, evaluated on status code.

    None of that changes the fact that you were impacted, and we apologize for that. In fairness, the change that shipped could have been made in a way that preserved the implicit status code precedence. Hyrum's Law in action.

    I've opened an internal issue to improve our documentation around these rough edges.

  2. jsoref commented on Aug 21, 2026

    @jsoref
    Author

    We highly recommend clients explicitly supply a version header to avoid breaking changes as older versions are sunset.

    https://docs.github.com/en/rest/about-the-rest-api/api-versions?apiVersion=2026-03-10#specifying-an-api-version

    If you specify an API version that is no longer supported, you will receive a 410 Gone response.

    I don't see how this would have been remotely better than me using an unversioned call. Sure, I could handle a 410 Gone, but what would I do?

    I've opened an internal issue to improve our documentation around these rough edges.

    thanks

    Areas such as status code precedence and error message bodies are considered undocumented behavior and not covered by our breaking change policy.

    Perhaps it'd be nice to mention which sort of things aren't covered by the promise.

    That said, for probes like this, I understand they're fragile and, as they're probes, I certainly wasn't particularly surprised that they broke (this isn't the first GitHub prove of mine that broke, and I don't expect it'll be the last unless I stop using github). I may, in the future, add some other mechanism to turn off probes, although I think I'm relatively happy with "fail safely" vs "fail open (dangerous)".

    Beyond that, I'd much rather have a way to ask the question that I want answered, but I don't seem to have a direct line to teams to get this sort of thing.

    A more deterministic check is a valid call made with a token carrying the expected scopes, guaranteed to return a 403, evaluated on status code.

    I'm not sure what you mean by expected scopes. My goal was (and still is) to determine whether I have write access. In any event, I don't want the repository to change state. I don't know of a way for me to ask "does this token have write access?" without making a change to the repository in the case that I do have write access.

    My new approach is different, but still undesirable:
    check-spelling/check-spelling@c11aa0b
    it tries to change the description (which unfortunately is technically user observable, and also racy) and if it succeeds, it tries to change it back (which means there are two or three ways for it to win/lose races with a human observer or editor).

  3. shawnHartsell commented on Aug 21, 2026

    @shawnHartsell
    Contributor

    Perhaps it'd be nice to mention which sort of things aren't covered by the promise.

    Agreed and that's the improvement we'll make on our side! Thanks again for the feedback 😁. Was there anything else you'd like to discuss? If not I'll go ahead and close this out.

  4. jsoref commented on Aug 21, 2026

    @jsoref
    Author

    I'd love to hear what you meant by expected scopes... I really do want to know if there's a way to do what I'm trying to do that's better than what I'm doing.

    That said, I'll file another bug about another of these related things. They're all more or less interrelated. A call would be simpler.

  5. shawnHartsell commented on Aug 21, 2026

    @shawnHartsell
    Contributor

    I don't see how this would have been remotely better than me using an unversioned call. Sure, I could handle a 410 Gone, but what would I do?

    Apologies, I meant to reply to this as well.

    Specifying a version does not make the eventual 410 Gone better. It prevents the client from silently moving to a version with breaking changes. A pinned client stays on its selected version until that version is retired, at which point the 410 makes the required upgrade explicit.

    For example, suppose /foo has versions 2026-01-01 and 2026-02-02, and requests without a version header use 2026-01-01. Version 2026-03-01 then introduces a breaking change to /foo. When 2026-01-01 retires, unpinned clients move to 2026-02-02 and continue working. When 2026-02-02 retires, those clients move to 2026-03-01 and break. Pinning avoids this.

    As a pinned version approaches retirement, sunset headers give clients time to detect the change and migrate. The trade-off is explicit lifecycle management versus automatic upgrades with the risk of hitting a breaking change.

    I'd love to hear what you meant by expected scopes... I really do want to know if there's a way to do what I'm trying to do that's better than what I'm doing.

    I may have misunderstood your intent on first read. I mentioned expected scopes only as a potential way to make the call more deterministic. Sorry for the confusion!

  6. jsoref commented on Aug 21, 2026

    @jsoref
    Author

    As a pinned version approaches retirement, sunset headers give clients time to detect the change and migrate. The trade-off is explicit lifecycle management versus automatic upgrades with the risk of hitting a breaking change.

    Pinning is a mess, especially for actions (which is what I do). You can easily have 6 layers of actions, each with some levels (the limit is 10 per https://docs.github.com/en/actions/concepts/workflows-and-actions/reusing-workflow-configurations) of pinning and delays in upgrading/releasing.

    I still regularly run into repositories using actions/checkout@v3 (there are 2.4M files...), there are 200k items using actions/checkout@v1 (2m for v2).

    Simple case:

    1. check-spelling pins to version x of the api,
    2. someone writes a wrapper for check-spelling and pins check-spelling (pinning being a "best practice" -- I know of at least one wrapper someone wrote for check-spelling, although I'm not sure if it's still in use),
    3. someone writes a workflow that pins their wrapper.

    Then GitHub kills an api, and in order for me to get an update to customers, each of us has to get a release out in the right order. But, realistically, I don't think GitHub kills an api, it sounds like you envision killing the entire versioned api, which means conceptually I have a single global flag for my app saying "I use 2022" and then suddenly in 2025 that version of check-spelling stops working because someone didn't upgrade it, not because the apis are really broken, but because in 2023 you deprecated the 2022 api, and in 2025 you removed it (this is hypothetical, in the real world, you're way more likely to break my app a dozen times based on undocumented api breaks than anyone is to get to this 410 case). I mean, there are 280 files with v0.0.19 which was released in 2021 (it has a GHSA) -- they're almost entirely in abandoned forks or archived repositories, but, they're still more or less there.

    Sometimes the api path that breaks is a commonly used path, but, potentially it could be a rarely used path. Me changing my app's version from 2022 to 2026 doesn't magically help identify that the app will actually work with the new path. -- Yes, I'm slowly adding coverage for things, but.... And, since I'm maintaining an action, unless I add lots of telemetry (which is problematic, especially since I want people to be able to run it on private repositories with private data), I don't have a great way to collect "oops, this didn't behave as expected in some previously unknown edge case". I do try to catch things and suggest people file tickets, but other than one or two cases (such as this show stopper), it hasn't turned much up.

    Oddly, in my case it was probably easier for my action to fail fast and quickly (more or less). I really don't see how this would have helped here. What would I have seen? What would real users have seen?

    I can already cause an old version of my action to die w/o using pinning (I've used it, I used it here, and I used it more recently for another unrelated case...). In fact, I could add a basic time bomb into my releases that make them only last for 2 years (web browsers historically had one, see Netscape 0.9's), but it's unclear that's a particularly good strategy.

    Now, I could write a little thing that lets my app check and see what version of the api it should use (just as it checks to see if it's still safe to run at all) and have it dynamically select the api that I've "validated", but that adds overhead to each run, and as I don't really want to support old versions of check-spelling (beyond saying "safe to use" vs "please upgrade" vs "not safe to use -- upgrade!") forever, I'm not sure what I gain by doing anything additional.


  7. shawnHartsell commented on Aug 21, 2026

    @shawnHartsell
    Contributor

    Thanks for the feedback. As I mentioned, the use of explicit version headers has trade-offs and it's clear there's a preference towards the roll forward behavior when not sending one 😀

    Since there's noting more to discuss regarding the original concern I'm going to go ahead and close this issue out. Thanks again!

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions