Repository navigation
GitHub's docs, schemas, source comments and runtime disagree: what #35 measured and what to file where #47
Description
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:
- Verify snapshot.if context availability and follow up on documentation
- Verify github.job_workflow_sha documentation and runtime behavior
- Reconcile numeric expression parsing with measured GitHub behavior
- Correct the v1.5.2 changelog examples for numeric literals
- Measure expression support in step and reusable-workflow uses
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.
0x1Fand0o17are accepted,0X1Fand0O17are rejected, while1E+5works. - Numbers do not survive a round trip. GitHub renders them .NET style at fifteen significant digits, so
1e-5becomes1E-05,1e0123becomes1E+123, and9007199254740993becomes9.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
numberliteral 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 withUnexpected symbol.Two further behaviours the page does not mention. The base prefix is case sensitive —
0x1Fand0o17work,0X1Fand0O17do not — while the exponent marker is not, since1E+5works. And a number that reaches a string does not come back as written: GitHub renders numbers .NET style at fifteen significant digits, so1e-5renders as1E-05and9007199254740993as9.00719925474099E+15.Method: each literal was placed in a
run-name:and in arun: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.ParseNumbersays it follows JavaScript'sNumber(), which it does not
ExpressionUtility.cs#L189-L192says the rules follow "the Number() function in Javascript".Measured on GitHub Actions, four forms
Number()accepts are rejected:0X1F,0O17,0b101and1e309. 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
0xand0oprefixes plus theNaNandInfinitykeywords.
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_failureexists and is not used. - The run has no jobs, and
latest_check_runs_countis 0. /actions/runs/{id}/annotations,/check-suites/{id}/annotationsandcommits/{sha}/statusesare all empty or 404.gh run view --logreportslog 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 --logreportslog 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_failureconclusion 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/refsanswers 403Resource 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_TOKENcannot be granted theworkflowspermission its own error message asks forA workflow job cannot create or update any file under
.github/workflowswithGITHUB_TOKEN, over git or over the Git Data API. Over git the message isrefusing to allow a GitHub App to create or update workflow '<path>' without 'workflows' permission. Over the API, blob, tree and commit writes succeed andPOST /git/refsreturns 403.
workflowsis not a key thepermissions: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 scopedGITHUB_TOKEN.Either the scope should be grantable in
permissions:— presumably write-only, likeid-token— or the message should say that the operation requires a token other thanGITHUB_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.jsonis behind the one inactions/languageservices
src/Sdk/WorkflowParser/workflow-v1.0.jsondefines seventeen permission scopes. The same file inactions/languageservicesdefines twenty. Missing here:copilot-requests,drivesandcode-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_TOKENpermissions table is missing four scopes and cannot express write-only scopes
data/reusables/actions/github-token-available-permissions.mdlists sixteen scopes. The workflow-parser schema inactions/languageservicesdefines twenty. The four not documented arecopilot-requests,drives,modelsandrepository-projects.The table also presents every scope as taking
read,writeornone, which is not the case. The schema typescopilot-requestsandid-tokenas write-or-none, andmodelsandvulnerability-alertsaccept onlyreadornone.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:modelsandvulnerability-alertsaccept onlyreadornone, andcopilot-requestsaccepts onlywriteornone. 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.jsonis missing three permission scopes and cannot express write-only scopesThe schema defines seventeen scopes under
permissions. GitHub's own workflow-parser schema inactions/languageservicesdefines twenty. Missing:copilot-requests,drivesandvulnerability-alerts.Every scope here is modelled with the same
read/write/noneenumeration. GitHub's schema has a separatepermission-level-write-or-no-accesstype used forcopilot-requestsandid-token, andmodelsandvulnerability-alertsaccept onlyreadandnone.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.jsondescribesdependency-type: allwith an outdated ecosystem listThe
x-intellij-enum-metadatadescription forallreads "For bundler, pip, composer, cargo, also the dependencies of direct dependencies." GitHub's Dependabot options reference lists six ecosystems for that behaviour, includinggomodanduv. Theindirectdescription 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