Skip to content

Adding label results in: json: cannot unmarshal number into Go struct field label.id of type string #8652

Description

@kwk

Describe the bug

This command used to work with gh in version 2.36.0.

$ gh --repo fedora-llvm-team/llvm-snapshots label create arch/x86_64 --color C5DEF5 --force
✓ Label "arch/x86_64" created in fedora-llvm-team/llvm-snapshots

grafik

After updating to version 2.43.0 the same command results in an error:

$ gh --repo fedora-llvm-team/llvm-snapshots label create arch/x86_64 --color C5DEF5 --force
json: cannot unmarshal number into Go struct field label.id of type string

grafik

Steps to reproduce the behavior

  1. Run gh --repo <YOURREPOHERE> label create arch/x86_64 --color C5DEF5 --force
  2. Notice the error: json: cannot unmarshal number into Go struct field label.id of type string

Expected vs actual behavior

I want a label to be created without an error as it was with gh in version 2.36.0.

Logs

I've enabled the debug output and here is the output:

$ GH_DEBUG=1  gh --repo fedora-llvm-team/llvm-snapshots label create "arch/x86_64" --color C5DEF5 --force
⣾* Request at 2024-01-31 18:09:06.3070798 +0100 CET m=+0.036524162
* Request to https://api.github.com/repos/fedora-llvm-team/llvm-snapshots/labels
⣻* Request took 351.745197ms
* Request at 2024-01-31 18:09:06.667054442 +0100 CET m=+0.396498854
⢿* Request to https://api.github.com/repos/fedora-llvm-team/llvm-snapshots/labels/arch/x86_64
⣟* Request took 291.129669ms
json: cannot unmarshal number into Go struct field label.id of type string

Activity

  1. added
    bugSomething isn't working
    on Jan 31, 2024
  2. added a commit that references this issue on Jan 31, 2024
  3. williammartin commented on Jan 31, 2024

    @williammartin
    Member

    Thanks for opening this bug and sorry for the inconvenience.

    Looks likely related to #8516 @andyfeller can you look at this?

  4. williammartin commented on Jan 31, 2024

    @williammartin
    Member

    @kwk can you provide the output of the failing command with GH_DEBUG=api please.

  5. kwk commented on Jan 31, 2024

    @kwk
    Author

    @williammartin sure:

    $ GH_DEBUG=api  gh --repo fedora-llvm-team/llvm-snapshots label create "arch/x86_64" --color C5DEF5 --force
    ⣾* Request at 2024-01-31 18:11:49.010277398 +0100 CET m=+0.029303388
    * Request to https://api.github.com/repos/fedora-llvm-team/llvm-snapshots/labels
    > POST /repos/fedora-llvm-team/llvm-snapshots/labels HTTP/1.1
    > Host: api.github.com
    > Accept: application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview
    > Authorization: token ████████████████████
    > Content-Length: 56
    > Content-Type: application/json; charset=utf-8
    > Time-Zone: Europe/Berlin
    > User-Agent: GitHub CLI 2.43.0
    
    {
      "color": "C5DEF5",
      "description": "",
      "name": "arch/x86_64"
    }
    
    ⢿< HTTP/2.0 422 Unprocessable Entity
    < Access-Control-Allow-Origin: *
    < Access-Control-Expose-Headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset
    < Content-Length: 182
    < Content-Security-Policy: default-src 'none'
    < Content-Type: application/json; charset=utf-8
    < Date: Wed, 31 Jan 2024 17:11:49 GMT
    < Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
    < Server: GitHub.com
    < Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
    < Vary: Accept-Encoding, Accept, X-Requested-With
    < X-Accepted-Oauth-Scopes: 
    < X-Content-Type-Options: nosniff
    < X-Frame-Options: deny
    < X-Github-Api-Version-Selected: 2022-11-28
    < X-Github-Media-Type: github.merge-info-preview; param=nebula-preview; format=json
    < X-Github-Request-Id: 2538:19A112:73F68C2:751C61D:65BA7F55
    < X-Oauth-Client-Id: 178c6fc778ccc68e1d6a
    < X-Oauth-Scopes: admin:public_key, gist, read:org, repo
    < X-Ratelimit-Limit: 5000
    < X-Ratelimit-Remaining: 4982
    < X-Ratelimit-Reset: 1706722516
    < X-Ratelimit-Resource: core
    < X-Ratelimit-Used: 18
    < X-Xss-Protection: 0
    
    {
      "message": "Validation Failed",
      "errors": [
        {
          "resource": "Label",
          "code": "already_exists",
          "field": "name"
        }
      ],
      "documentation_url": "https://docs.github.com/rest/issues/labels#create-a-label"
    }
    
    * Request took 363.209682ms
    * Request at 2024-01-31 18:11:49.379709535 +0100 CET m=+0.398735524
    * Request to https://api.github.com/repos/fedora-llvm-team/llvm-snapshots/labels/arch/x86_64
    > PATCH /repos/fedora-llvm-team/llvm-snapshots/labels/arch/x86_64 HTTP/1.1
    > Host: api.github.com
    > Accept: application/vnd.github.merge-info-preview+json, application/vnd.github.nebula-preview
    > Authorization: token ████████████████████
    > Content-Length: 18
    > Content-Type: application/json; charset=utf-8
    > Time-Zone: Europe/Berlin
    > User-Agent: GitHub CLI 2.43.0
    
    {
      "color": "C5DEF5"
    }
    
    ⣟< HTTP/2.0 200 OK
    < Access-Control-Allow-Origin: *
    < Access-Control-Expose-Headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset
    < Cache-Control: private, max-age=60, s-maxage=60
    < Content-Security-Policy: default-src 'none'
    < Content-Type: application/json; charset=utf-8
    < Date: Wed, 31 Jan 2024 17:11:49 GMT
    < Etag: W/"9b05ddd9ed668ffe3d0e01c86a0c84d0faaa1cbbb6e1727fd757a4d21b5c860b"
    < Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
    < Server: GitHub.com
    < Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
    < Vary: Accept, Authorization, Cookie, X-GitHub-OTP
    < Vary: Accept-Encoding, Accept, X-Requested-With
    < X-Accepted-Oauth-Scopes: 
    < X-Content-Type-Options: nosniff
    < X-Frame-Options: deny
    < X-Github-Api-Version-Selected: 2022-11-28
    < X-Github-Media-Type: github.merge-info-preview; param=nebula-preview; format=json
    < X-Github-Request-Id: 2538:19A112:73F69E9:751C75C:65BA7F55
    < X-Oauth-Client-Id: 178c6fc778ccc68e1d6a
    < X-Oauth-Scopes: admin:public_key, gist, read:org, repo
    < X-Ratelimit-Limit: 5000
    < X-Ratelimit-Remaining: 4981
    < X-Ratelimit-Reset: 1706722516
    < X-Ratelimit-Resource: core
    < X-Ratelimit-Used: 19
    < X-Xss-Protection: 0
    
    {
      "id": 6175199621,
      "node_id": "LA_kwDOFBZt3s8AAAABcBIRhQ",
      "url": "https://api.github.com/repos/fedora-llvm-team/llvm-snapshots/labels/arch/x86_64",
      "name": "arch/x86_64",
      "color": "C5DEF5",
      "default": false,
      "description": ""
    }
    
    * Request took 304.566588ms
    json: cannot unmarshal number into Go struct field label.id of type string
  6. williammartin commented on Jan 31, 2024

    @williammartin
    Member

    Thanks, pretty suspicious line change here: c2f6a4e#diff-b919d95405a6827b4deaafd94935ed8158e4333cd308181bae75982f3526cb40L24

    Unfortunately the author is no longer on the team or with GitHub so will have to try and understand this change without him.

  7. kwk commented on Jan 31, 2024

    @kwk
    Author

    Thanks, pretty suspicious line change here: c2f6a4e#diff-b919d95405a6827b4deaafd94935ed8158e4333cd308181bae75982f3526cb40L24

    Unfortunately the author is no longer on the team or with GitHub so will have to try and understand this change without him.

    Well, I'd say the StructExportData function being used instead of the reflection doesn't sound all too good:

    // Basic function that can be used with structs that need to implement
    // the exportable interface. It has numerous limitations so verify
    // that it works as expected with the struct and fields you want to export.
    // If it does not, then implementing a custom ExportData method is necessary.
    // Perhaps this should be moved up into exportData for the case when
    // a struct does not implement the exportable interface, but for now it will
    // need to be explicitly used.

    (source: https://github.com/cli/cli/blob/trunk/pkg/cmdutil/json_flags.go#L267C1-L273C31)

    I wonder why there's no test checking if the API still works. This error itself should have been caught by CI, no?

  8. williammartin commented on Jan 31, 2024

    @williammartin
    Member

    I wonder why there's no test checking if the API still works. This error itself should have been caught by CI, no?

    That should be the case but doesn't look to be.

    Well, I'd say the StructExportData function being used instead of the reflection doesn't sound all too good:

    Not familiar with this function so can't say yet but in any case the error occurs earlier.

    We can see that the label struct says that Id should be a string:

    ID string `json:"id"`

    But from your request the Id is clearly a number:

    {
      "id": 6175199621,
      "node_id": "LA_kwDOFBZt3s8AAAABcBIRhQ",
      "url": "https://api.github.com/repos/fedora-llvm-team/llvm-snapshots/labels/arch/x86_64",
      "name": "arch/x86_64",
      "color": "C5DEF5",
      "default": false,
      "description": ""
    }
    

    So it's failing trying to unmarshal the response here:

    cli/pkg/cmd/label/create.go

    Lines 176 to 177 in e461c89

    result := label{}
    err = apiClient.REST(repo.RepoHost(), "PATCH", path, requestBody, &result)


    The obvious thing to do here is change the struct tag back and ship a patch release but I'm just hesitant because I don't understand the reason it was changed in the first place.

  9. williammartin commented on Jan 31, 2024

    @williammartin
    Member

    Alright I think I sort of see what might have happened here and it's a result of sharing a struct that probably shouldn't be. Here's the label struct:

    type label struct {
    Color string `json:"color"`
    CreatedAt time.Time `json:"createdAt"`
    Description string `json:"description"`
    ID string `json:"id"`
    IsDefault bool `json:"isDefault"`
    Name string `json:"name"`
    URL string `json:"url"`
    UpdatedAt time.Time `json:"updatedAt"`
    }

    It gets used when listing labels via gh label list which hits the graphql API:

    Nodes []label

    It also gets used for the response from creating or updating a label using gh label create which hits the REST API:

    cli/pkg/cmd/label/create.go

    Lines 140 to 141 in e461c89

    result := label{}
    err = apiClient.REST(repo.RepoHost(), "POST", path, requestBody, &result)

    The problem is that the responses from these APIs are not the same schema.

    Here's the graphql object and we can see that it does have an id field with type ID (which is effectively string): https://docs.github.com/en/graphql/reference/objects#label

    And here's the REST object which has a number id and a node_id string: https://docs.github.com/en/rest/issues/labels?apiVersion=2022-11-28#create-a-label


    I suspect that the author of the change didn't look too closely at the usage of create since that PR was mostly about variable list. I don't know why they decided to make a change in the labels code. I also have no idea why these commands use REST sometimes and graphql at others but there's probably some legacy reason since they've been around for a long time.

    The correct course of action here seems to be to split this type into two.

    Although, I don't really understand why create/update needs this type at all since the request result seems to get thrown away so maybe we can just stop using it.

  10. williammartin commented on Jan 31, 2024

    @williammartin
    Member

    @kwk if you happen to be up for building from source could you try out this branch and see if it resolves your issue (and doesn't introduce anything else new). It seems to on my end.

  11. added
    priority-1Affects a large population and inhibits work
    and removed on Jan 31, 2024
  12. andyfeller commented on Jan 31, 2024

    @andyfeller
    Contributor

    @kwk : finished releasing v2.43.1 which should address this, however it won't be picked up by brew for an hour depending on subsequent PRs and scheduled automation on the brew side.

  13. kwk commented on Jan 31, 2024

    @kwk
    Author

    @williammartin and @andyfeller I can confirm that https://github.com/cli/cli/releases/tag/v2.43.1 fixed the issue for me. Thank you for creating a fix so quickly!

    @williammartin you've mentioned this test here:

    --- FAIL: TestCreateRun (0.00s)
        --- FAIL: TestCreateRun/creates_label_(EXPECTED_TO_BREAK_FOR_DEMONSTRATION_PURPOSES) (0.00s)
            /Users/williammartin/workspace/cli/pkg/cmd/label/create_test.go:179: 
                	Error Trace:	/Users/williammartin/workspace/cli/pkg/cmd/label/create_test.go:179
                	Error:      	Received unexpected error:
                	            	json: cannot unmarshal number into Go struct field label.id of type string
                	Test:       	TestCreateRun/creates_label_(EXPECTED_TO_BREAK_FOR_DEMONSTRATION_PURPOSES)
    

    Did this test ever pass before your patch? I mean this looks like a CI test that should have blocked the old PR in the first place, shouldn't it?

    Anyways, thank you again so much! Feel free to close the issue.

  14. williammartin commented on Feb 1, 2024

    @williammartin
    Member

    @kwk I wrote that test to demonstrate the issue programmatically. There was no existing test that covered this. Here's the PR from 2 years ago when label create was added: #5316

    You can see that the response was unmarshaled but never used: https://github.com/ZaiZheTingDun/cli/blob/ff75841657b402cdda63b7f0b7e8bf90d668ac58/pkg/cmd/label/create/create.go#L131-L133

    And so presumably the tests were written to match the idea that we didn't care about the data: https://github.com/ZaiZheTingDun/cli/blob/ff75841657b402cdda63b7f0b7e8bf90d668ac58/pkg/cmd/label/create/create_test.go#L101

    Unfortunately no one noticed that even though we don't care about the values of the response, there was a possible error path in the unmarshaling with regards the schema. Of course we'd like to have coverage of every branch but things will slip through. One of my focuses for this year is to think about how we can improve our confidence around API interactions.

    Anyway, thanks for the confirmation and very sorry that we broke you.

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

Metadata

Metadata

Labels

bugSomething isn't workingpriority-1Affects a large population and inhibits work

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions