Skip to content

GitHub's docs, schemas, source comments and runtime disagree: what #35 measured and what to file where #47

Description

@kjanat

Reporting completed — September 17, 2026

All eight upstream findings have linked follow-ups or an existing report. SchemaStore #6357 and #6358 are merged; github/docs #45881 and #45904 remain open and ready for review. Remaining investigations are tracked separately:

Closing this reporting register; upstream outcomes remain tracked in the linked reports.


Upstream findings — September 15, 2026

Register: #47

Finding Disposition
1. Number literal documentation Existing PR github/docs#45881
2. String-to-number coercion Filed actions/runner#4724 after fresh six-case run 34972018030; uppercase hex/octal and binary differ. String 1e309 DOES become Infinity.
3. Workflow parser errors missing from run API Added current endpoint evidence to existing community/community#182315 (comment)
4. GITHUB_TOKEN workflow-file permission Existing report https://github.com/orgs/community/discussions/26711 covers the exact problem. No duplicate. Alternative GitHub App credentials also exist; original claim that PATs are the only workaround is incorrect.
5. Runner permission schema Filed actions/runner#4723 for documented code-quality and copilot-requests. drives excluded until publicly available.
6. Permission docs PR github/docs#45904 adds copilot-requests. models and classic projects were intentionally removed; id-token/vulnerability-alerts restrictions already documented.
7. SchemaStore permissions PR SchemaStore/schemastore#6358 restricts id-token to write/none, with positive/negative fixtures. copilot-requests and vulnerability-alerts already fixed upstream.
8. Dependabot tooltip PR SchemaStore/schemastore#6357 adds gomod/uv.

The original findings below are historical. In particular, finding 2 conflated literal parsing with string coercion; findings 5–7 included scopes already fixed, retired or not yet public. The linked follow-ups use rechecked evidence.


The expression conformance probe submits workflow expressions to GitHub's own parser and reads back the verdict, so behaviour can be measured instead of inferred from source. Building it turned up a set of places where GitHub's documentation, GitHub's own source comments, GitHub's schemas and GitHub's runtime disagree with each other.

Several of these are worth reporting upstream. This issue is the register of what we found, where each one belongs, and text ready to file. The original register below predates the September 15 follow-up; see the current status above.

Every claim below was measured unless it says otherwise. Where a finding only affects how we lint, it is listed at the end as ours to carry rather than theirs to fix.


1. The documented number-literal rule is wrong in both directions

File at: github/docs

The expressions reference defines a number literal as "Any number format supported by JSON".

Measured against GitHub's parser with 44 literals, eight forms that are not valid JSON are accepted, and one form that is valid JSON is rejected.

literal GitHub valid JSON?
0x1F 31 no
0x01 1 no
0o17 15 no
0123 123 no
+1 1 no
.5 0.5 no
1. 1 no
NaN NaN no
Infinity Infinity no
1e309 rejected yes

Two adjacent surprises found in the same run, worth including because they are not documented anywhere:

  • The base prefix is case sensitive; the exponent marker is not. 0x1F and 0o17 are accepted, 0X1F and 0O17 are rejected, while 1E+5 works.
  • Numbers do not survive a round trip. GitHub renders them .NET style at fifteen significant digits, so 1e-5 becomes 1E-05, 1e0123 becomes 1E+123, and 9007199254740993 becomes 9.00719925474099E+15.

Proposed issue text

Title: The expressions reference describes number literals as JSON, and the runner accepts and rejects different things

The expressions reference says a number literal is "Any number format supported by JSON". Measured against GitHub Actions itself, that rule is wrong in both directions.

Accepted by GitHub and not valid JSON: 0x1F → 31, 0x01 → 1, 0o17 → 15, 0123 → 123, +1 → 1, .5 → 0.5, 1. → 1, NaN, Infinity.

Valid JSON and rejected by GitHub: 1e309, which fails to parse with Unexpected symbol.

Two further behaviours the page does not mention. The base prefix is case sensitive — 0x1F and 0o17 work, 0X1F and 0O17 do not — while the exponent marker is not, since 1E+5 works. And a number that reaches a string does not come back as written: GitHub renders numbers .NET style at fifteen significant digits, so 1e-5 renders as 1E-05 and 9007199254740993 as 9.00719925474099E+15.

Method: each literal was placed in a run-name: and in a run: step in its own workflow file, dispatched, and the rendered value read back from the run. Rejections were confirmed from the HTTP 422 body of the workflow dispatch endpoint, which carries the parser's own message. Full matrix and the runs: <link to PR #35>.

Suggestion: replace the JSON sentence with the grammar the parser implements, or state plainly which JSON forms are excluded and which non-JSON forms are accepted.


2. The runner's own comment says it follows JavaScript, and it does not

File at: actions/runner

ExpressionUtility.cs, three lines above the parse:

The rules here attempt to follow Javascript rules for coercing a string into a number for comparison. That is, the Number() function in Javascript.

Number() handles 0X1F as 31, 0O17 as 15, 0b101 as 5 and 1e309 as Infinity. GitHub rejects all four. There is a hex branch and an octal branch and no binary one.

This one matters more than a stale comment usually would, because the two statements point in opposite directions and third parties have picked a side and built on it. The upstream actionlint project closed an issue about NaN and Infinity on the strength of the documentation's JSON sentence — and measured, GitHub accepts both.

Proposed issue text

Title: The comment on ExpressionUtility.ParseNumber says it follows JavaScript's Number(), which it does not

ExpressionUtility.cs#L189-L192 says the rules follow "the Number() function in Javascript".

Measured on GitHub Actions, four forms Number() accepts are rejected: 0X1F, 0O17, 0b101 and 1e309. The hex and octal branches are keyed on the lower-case prefix only, and there is no binary branch.

The documentation says something different again — the expressions reference calls a number "Any number format supported by JSON" — and neither statement describes what the runner does. Tool authors read both: upstream actionlint closed rhysd#79 citing the JSON sentence, and the forms it rejected on that basis are accepted by GitHub.

Suggestion: the comment could name the actual grammar, which appears to be a decimal/exponent form plus lower-case 0x and 0o prefixes plus the NaN and Infinity keywords.


3. A workflow rejected by the parser reports no reason through any REST endpoint

File at: GitHub Community Discussions, Actions category, or GitHub Support if you have a plan that routes API bugs there. There is no public issue tracker for the Actions service itself.

When a workflow file contains an expression the parser cannot read, the whole file is rejected. Measured against a real failed run:

  • The run's conclusion is failure. startup_failure exists and is not used.
  • The run has no jobs, and latest_check_runs_count is 0.
  • /actions/runs/{id}/annotations, /check-suites/{id}/annotations and commits/{sha}/statuses are all empty or 404.
  • gh run view --log reports log not found.

The message exists in the web UI, and — this is the odd part — it is also available from a completely different endpoint. POST /repos/{owner}/{repo}/actions/workflows/{id}/dispatches against a ref whose workflow does not parse answers HTTP 422 with the parser's own text:

Invalid Argument - failed to parse workflow: (Line: 2, Col: 11): Unexpected symbol: '1e+'. Located at position 1 within expression: 1e+

So the API will tell a client why a file does not parse, but not through any endpoint that describes the run which failed because of it.

Proposed report text

Title: A workflow that fails to parse exposes no reason through the REST API

A workflow file containing an unparseable expression produces a run with conclusion failure, no jobs, no check runs, no annotations and no logs. Every REST endpoint that would normally carry the reason is empty or 404: /actions/runs/{id}/annotations, /check-suites/{id}/annotations, commits/{sha}/statuses, and the log endpoint. gh run view --log reports log not found. The parser's message appears only in the web UI.

The same message is returned by POST /repos/{owner}/{repo}/actions/workflows/{id}/dispatches, which answers 422 with the full parser text including line and column. So the information is available over the API, just not from anything describing the run.

Two things would help, either separately: use the startup_failure conclusion that already exists for exactly this case, and surface the parser message as an annotation the way other setup failures are.


4. GITHUB_TOKEN needs a permission the permissions: block cannot express

File at: GitHub Community Discussions, Actions category. Possibly also github/docs for the narrower documentation half.

Any ref update touching a path under .github/workflows is refused for GITHUB_TOKEN. Measured over both transports:

  • git push: refusing to allow a GitHub App to create or update workflow '.github/workflows/<name>.yaml' without 'workflows' permission
  • Git Data API: blob, tree and commit all succeed, then POST /git/refs answers 403 Resource not accessible by integration

There is no way to grant it. workflows is not in the workflow-syntax permissions table, nor in the workflow-parser schema. It exists for personal access tokens and for GitHub App installation permissions, and nowhere in workflow YAML.

The refusal message names a permission and there is no way to act on it. This is the second instance of the same shape we found; the other is that GET /repos/{owner}/{repo}/actions/permissions/workflow — the endpoint describing GITHUB_TOKEN's own defaults — requires Administration, which is likewise not a workflow scope.

Proposed report text

Title: GITHUB_TOKEN cannot be granted the workflows permission its own error message asks for

A workflow job cannot create or update any file under .github/workflows with GITHUB_TOKEN, over git or over the Git Data API. Over git the message is refusing to allow a GitHub App to create or update workflow '<path>' without 'workflows' permission. Over the API, blob, tree and commit writes succeed and POST /git/refs returns 403.

workflows is not a key the permissions: block accepts. It is not in the workflow-syntax permissions table and not in the workflow-parser schema. The error therefore names a permission that a workflow author has no way to grant, and the only workaround is a personal access token in a secret, which is a strictly worse security posture than a scoped GITHUB_TOKEN.

Either the scope should be grantable in permissions: — presumably write-only, like id-token — or the message should say that the operation requires a token other than GITHUB_TOKEN.


5. Three GitHub-owned sources disagree on the permission scope list

File at: actions/runner for the stale copy. The documentation half is finding 6.

actions/languageservices, workflow-parser/src/workflow-v1.0.json, defines twenty permission scopes.

actions/runner, src/Sdk/WorkflowParser/workflow-v1.0.json, is a copy of that schema and defines seventeen. It lacks copilot-requests, drives and code-quality.

Two GitHub-owned repositories therefore ship two different versions of one schema.

Proposed issue text

Title: The vendored copy of workflow-v1.0.json is behind the one in actions/languageservices

src/Sdk/WorkflowParser/workflow-v1.0.json defines seventeen permission scopes. The same file in actions/languageservices defines twenty. Missing here: copilot-requests, drives and code-quality.

Either the copy could be refreshed, or the two repositories could share one source so they cannot drift again.


6. The documented permissions table omits four scopes the schema defines

File at: github/docs

data/reusables/actions/github-token-available-permissions.md lists sixteen scopes. The schema defines twenty. Missing from the documentation: copilot-requests, drives, models and repository-projects.

Re-checked against the rendered page while writing this: the table still shows exactly those sixteen, and all four are still absent.

The table also cannot express a write-only scope. The schema types copilot-requests and id-token as permission-level-write-or-no-access, and models and vulnerability-alerts accept only read and none. The documentation presents every scope as accepting read, write or none.

That second half matters beyond the table. The permissive default token is usually described, in docs and in tooling, as write on every scope except id-token. Taken literally that set does not exist: models and vulnerability-alerts accept only read or none, and copilot-requests accepts only write or none. We could not find a verbatim GitHub sentence to quote for this, so it is stated as the common description rather than attributed.

Proposed issue text

Title: The GITHUB_TOKEN permissions table is missing four scopes and cannot express write-only scopes

data/reusables/actions/github-token-available-permissions.md lists sixteen scopes. The workflow-parser schema in actions/languageservices defines twenty. The four not documented are copilot-requests, drives, models and repository-projects.

The table also presents every scope as taking read, write or none, which is not the case. The schema types copilot-requests and id-token as write-or-none, and models and vulnerability-alerts accept only read or none.

That second point has a knock-on effect. The permissive default token is commonly described as granting write on every scope except id-token, and taken literally that set does not exist: models and vulnerability-alerts accept only read or none, and copilot-requests accepts only write or none. If the docs describe it that way anywhere, that wording could be adjusted too.


7. SchemaStore's workflow schema is three scopes behind

File at: SchemaStore/schemastore

src/schemas/json/github-workflow.json defines seventeen permission scopes and does not know copilot-requests, drives or vulnerability-alerts. It also models every scope with the same three-value enumeration, so it cannot express a write-only scope at all.

This is the schema most editors load for a workflow file, so a workflow GitHub accepts is flagged red in the editor.

Proposed issue text

Title: github-workflow.json is missing three permission scopes and cannot express write-only scopes

The schema defines seventeen scopes under permissions. GitHub's own workflow-parser schema in actions/languageservices defines twenty. Missing: copilot-requests, drives and vulnerability-alerts.

Every scope here is modelled with the same read/write/none enumeration. GitHub's schema has a separate permission-level-write-or-no-access type used for copilot-requests and id-token, and models and vulnerability-alerts accept only read and none.

Since this schema is what most editors load, a workflow GitHub accepts is currently reported as invalid in the editor.


8. SchemaStore's Dependabot schema describes dependency-type: all with a stale ecosystem list

File at: SchemaStore/schemastore

src/schemas/json/dependabot-2.0.json, in x-intellij-enum-metadata:

All explicitly defined dependencies. For bundler, pip, composer, cargo, also the dependencies of direct dependencies.

GitHub's Dependabot options reference lists six ecosystems for that behaviour, adding gomod and uv.

The field does not affect validation, so nothing is accepted or rejected differently. It renders as a tooltip in IntelliJ and VS Code at the moment somebody is deciding whether to write the key, and a reader taking it at face value concludes allow: [{dependency-type: all}] is a no-op for Go modules. It is not.

Proposed issue text

Title: dependabot-2.0.json describes dependency-type: all with an outdated ecosystem list

The x-intellij-enum-metadata description for all reads "For bundler, pip, composer, cargo, also the dependencies of direct dependencies." GitHub's Dependabot options reference lists six ecosystems for that behaviour, including gomod and uv. The indirect description has the same gap.

The text renders as a tooltip while somebody is deciding whether to write the key, so a reader concludes the filter does nothing for Go modules when it does.


Already reported by somebody else

jobs.<job_id>.snapshot.if is missing from the context-availability table. actions/runner's WorkflowTemplateConverter.cs allows github, vars, inputs, needs, strategy and matrix there; the table does not list the key at all. github/docs#41255 has been open since 2025-11-14. A comment with our measurement would be more use than a duplicate.

github.job_workflow_sha is documented and does not exist. It evaluates to null. A GitHub collaborator called it a documentation bug in actions/runner#2417 in 2023, and the issue was closed in 2026 by announcing four new job context properties instead. Public workflows still reference the old property and silently get an empty string. Worth a github/docs issue if the reference page still lists it; needs checking first.


Not theirs to fix — ours

Recorded here so the list is complete, not because anything should be filed.

Our lexer is stricter than GitHub on nine rules. #29 closed two of them. Fifteen forms GitHub accepts are still rejected, including the whole hex-with-leading-zero family and any integer above int32, since expr_parser.go parses with a bit size of 32. This is deliberate for most of them and an oversight for at least the int32 case.

CHANGELOG.md:2122 is wrong about both of its examples. The v1.5.2 note describes 0x01 and 1e0123 as invalid numbers. GitHub accepts both.

Nothing tells us whether an expression works in uses:. availability.go has no entry for it, so any context there is reported as an error. GitHub's own table is silent, and A7 shows that its silence is not proof. A uses: variant of the probe would settle it.


What would help before filing

The probe currently needs an EXPR_PROBE_TOKEN secret with contents and workflows write, for the reason in finding 4. With that in place it runs on demand and each of these can be re-measured against a current GitHub before the issue goes out, rather than against a snapshot from 2026-08-21.

Activity

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

    documentationImprovements or additions to documentationdone?idktype:taskDefined piece of work with a clear outcome

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions