Repository navigation
docs(agents): heal an unambiguous malformation at the boundary and refuse the rest - #464
Merged
Merged
Conversation
…fuse the rest The owner set a fleet-wide rule: a malformation with one unambiguous repair is healed where it is parsed and the repair reported, and only one with no single repair is refused. The hard rule said every fault the settings file alone reveals is refused, so an implementer following it would have refused what the rule now heals.
The refusal audit found that an enum value off only by case has exactly one repair, so the rule no longer lists every enum violation among the refusals.
Vivswan
enabled auto-merge (squash)
October 5, 2026 06:14
Vivswan
disabled auto-merge
October 5, 2026 06:14
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The reporting rule conflicts with the documented silent normalization exception.
Review effort: Balanced
Findings: 1
What changed in this PR
Updates repository guidance to distinguish healable malformed inputs from parse-time refusals.
Changes:
- Defines when malformed values are healed and reported.
- Clarifies when invalid values must be refused.
| File | Description |
|---|---|
AGENTS.md |
Revises parsing and healing policy. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+42
to
+43
| - A malformation in a settings file or an input with one unambiguous repair (an identical duplicate entry, surrounding whitespace, an enum value off only by case) is healed where it is parsed | ||
| and the repair reported in the run output, the healing step's comment stating this rule in one line. |
Contributor
File size check0 over a hard cap (fails), 34 warning(s).
Split the file, wrap the line, shorten or exempt the comment, or list the path in 5 managed file(s) skipped; repo-platform owns them. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What this changes
How
src/.Proof
Technical details
Heal classes (each yes row carries one tag)
Rulings
undeclaredwrapper key. Healing it is a compatibility alias, which the no-shims hard rule and docs/reference/undeclared-policy.md both forbid.settings-file: only.yml,. A path may contain a comma, so the stray separator has two readings.yes,no,on,offas booleans. The refusal text already calls them ambiguous.inputs()trims every input,affiliationand thesectionslists drop repeats, andtopicslowercases.The audit (paths under
src/, one row per refusal)<source> must be a YAML mapping of section names to settings, but its top level parsed as a <shape><source> must be a plain YAML mapping of section names to settings_xtop-level keyunknown underscore key: _x. The underscore marks this action's directives_undeclaredoutside keep/deleteKeeptokeephealed: _undeclared "Keep" read as "keep"unknown top-level section: x (known: ...). Fix the typo_remove: truein a single document<site>: a single document has no lower layer to remove fromsection: nullwhere the section has no off state<key>: null has no meaning; remove the section or declare its entries<path> is not plain YAML data (<what>); replace it with a plain value<path> refers back to one of its own containers (a YAML alias cycle).nanor.infin a passthrough field<path> is NaN, which JSON cannot carrynullwhere the key has no empty state<path> has no empty state; write <legal values><key>[i] (<field> "x"): declares "k", which this section does not recognize<label> carries a literal value, but settings files are committed plaintextprefix-$TOKEN<label> embeds $TOKEN without being a whole-value reference$INPUT_*,$GITHUB_*, and the other reserved prefixes<label> references the reserved runner variable $X<label> uses the secret reference $X in a target-fetched settings filecannot read settings from <path>: <reason>layer "x": the document contains a reference cyclelayer "x": <site> must be a mapping; got <kind>layer "x": <key> must be a list of mappings or an {_layering, entries} wrapper_layeringor_undeclaredoutside its setDeeptodeephealed: layer "x": _layering "Deep" read as "deep"_removenottrue"true"totrue(other values stay refused)healed: layer "x": <site>._remove "true" read as true_remove: truebeside other fieldslayer "x": <site> carries _remove: true beside "color"_removeunder_layering: replacelayer "x": <site> carries _remove: true, but under _layering: replace ...layer "x": <site> carries no string "name", which every entry needs to layer byhealed: layer "x": labels[2] repeats labels[0] exactly; kept onelayer "x": <site> carries _remove: true, but no lower layer declares an entry under its keysectionsallowlistthe "required-sections" entry "x" is excluded by the "sections" allowlistmode,on-missing-permission,private-repos,private-report,layering,undeclaredoff the setApplytoapplyhealed: the "mode" input "Apply" read as "apply"visibility,archived,forkserror: unknown option '--x'sectionsorrequired-sectionsunknown section "x" in the "sections" inputthe "report-public-key" input only applies to private-report: artifactprivate-report: artifact needs a "report-public-key" inputthe "report-public-key" input is not a valid age recipientcannot call the GitHub API: no token was providedrepositorythat is no slugcannot target a repository: "x" is not an owner/name slugthe "x" input does not apply to mode: renderrendered-filemode: render needs a "rendered-file" inputthe "settings-file" input is "", which lists no filethe "x" input does not apply to mode: snapshotsnapshot-fileandsnapshot-dirboth setthe "snapshot-file" and "snapshot-dir" inputs are both setmode: snapshot needs exactly one of the "snapshot-file" input ... or the "snapshot-dir" inputsnapshot-filewithreposthe "snapshot-file" input writes one repository's snapshot, but "repos" ... names multi-repo targetsrepositorywithsnapshot-dirthe "repository" input cannot be combined with "snapshot-dir"snapshot-dirwith no targetsthe "snapshot-dir" input needs multi-repo targetsthe "layering" input only applies to mode: renderthe "snapshot-file" input only applies to mode: snapshotprivate-report: artifact uploads the reports as a workflow artifactprivate-repos: showthe "private-report" input delivers reports only for redacted targetsrepositorywithreposthe "repository" input cannot be combined with "repos" or "repos-dir"settings-filewithreposthe "settings-file" input cannot be combined with "repos" or "repos-dir"repos: "*"the discovery filter input "visibility" only applies to repos: "*" discoverydefaults-filein single-repo modethe "defaults-file" input only applies to multi-repo modethe "settings-file" input is "x", which contains a list separator*beside explicit slugsthe "repos" input mixes "*" with explicit repositoriesthe "repos" input has 1 invalid entry: "x" is not an owner/name slughealed: the "repos" input lists "o/n" twice; kept one"x" is not an owner/name repository slugrepos-dir "x" does not exist in the workspacecannot read repos-dir "x"<file> resolves to the target "x", which is not a valid owner/name slugduplicate target o/n: defined by both <a> and <b>cannot resolve <files>: top-level repos-dir files use the current repository's ownerhealed: --mode given twice as "apply"; read once--force--forceundeclaredwrapper keythe wrapper's policy key "undeclared" was renamed to "_undeclared" in v3_xwrapper keythe wrapper's directives are "_undeclared" and ... nothing elseUnrecognized key: "entrys"Invalid input: expected a list of entries, or a mapping with "entries"_undeclaredor_layeringoff its setDeletetodeletehealed: labels._undeclared "Delete" read as "delete"the variable value is N bytes of UTF-8; GitHub caps a variable at 48 KBthe secret name "x" starts with a digit - GitHub accepts ...[A-Za-z0-9_]-stays refused)healed: secret name " TOKEN" trimmed to "TOKEN"javascriptortypescriptjavascript-typescript, collapsed when both appearhealed: languages "javascript" written as "javascript-typescript"rustin code quality"rust" is reported by GitHub but the PATCH cannot set it; remove ithealed: languages "Python" read as "python"schedule,updated_at"schedule" is reported by GitHub but the PATCH does not accept itrunner_type: labeledwithoutrunner_labelrunner_type: "labeled" needs a runner_labelrunner_labelwithoutrunner_type: labeledrunner_label "x" is declared ..., where GitHub ignores itan empty permission grants nothing"x" spans several lines; a permission is one line"push\n"healed: permission "push\n" trimmed to "push"read,write, any casereadtopull,writetopushhealed: permission "write" written as "push"Push,ADMINhealed: permission "ADMIN" read as "admin"healed: labels[2] repeats labels[0] exactly; kept oneInvalid input: expected a plain mappingselected_actions_url,maximum_allowed_days,sub_claim_prefix<field> is a value GitHub reports, not a setting it acceptsselected_actionswithoutallowed_actions: selectedselected_actions is declared together with allowed_actions: ...[A-Za-z0-9_]a claim key holds only letters, digits, and underscoreshealed: include_claim_keys repeats "repo" adjacently; kept oneinclude_claim_keysunderuse_default: trueGitHub ignores include_claim_keys under use_default: trueuse_defaultmissing or not a boolean"true"totrue(missing stays refused)healed: use_default "true" read as trueallowed_actions,default_workflow_permissions,access_level,approval_policyhealed: allowed_actions "All" read as "all"Unrecognized keyUnrecognized keyhealed: cache.max_cache_size_gb "10" read as 10run_workflows_from_fork_pull_requestsmissing""key_prefix is emptyhealed: key_prefix " TICKET-" trimmed<num>placeholderurl_template "x" has no "<num>" placeholderthe key_prefix "TICK" begins the key_prefix "TICKET-"force_push_bypassersactor@on an otherwise valid login (others stay refused)healed: force_push_bypassers "@octocat" read as "octocat"{login}in an actor listhealed: restrictions.users[0] written as "octocat"<site> lists each actor as its login stringusersorteams[]healed: protection.restrictions.teams added as []<site> lists each actor as its login stringprotection.restrictions must be a mapping of users, teams, and apps listsstrictmissing or not a boolean"true"totrue(missing stays refused)healed: strict "true" read as truecontextsnorchecksrequired_status_checks must list the required checksrequired_status_checks must be a mapping of its keyscheckseach required_status_checks.checks item is a {context, app_id} mappinga required_status_checks.checks item takes only context and app_idrequired_status_checks.checks[].context must be the check's nameapp_idhealed: app_id "15368" read as 15368healed: required_approving_review_count "2" read as 2required_pull_request_reviews must be a mapping of its keysenforce_admins: {enabled: true}{enabled: v}tovhealed: enforce_admins {enabled: true} written as trueurlor*_urlkey<site> is a link GitHub's GET response carriesname,enforcement_level, a strayenabled<site> is GitHub's GET-only echo"true"totruehealed: required_signatures "true" read as trueenvironmentsUnrecognized keyhealed: force_push_bypassers lists "octocat" twice; kept onehealed: required_deployments.environments lists "prod" twice; kept onethe wildcard entry "x" declares protection.<key>, which this section does not manageapp_idhealed: app_id "15368" read as 15368app_idhealed: auto_trigger_checks[1] repeats [0] exactly; kept onesettingmissing or not a boolean"true"totrue(missing stays refused)healed: setting "false" read as falsestate,query_suite,runner_type,threat_modelhealed: state "Configured" read as "configured"state,runner_type,ai_findings_optionhealed: state "Configured" read as "configured"originandexpires_atincludedinteraction_limits takes limit, expiry, ...; remove the key, or fix its spellinghealed: limit "Existing_Users" read as "existing_users""true"totruehealed: enabled "true" read as truehealed: max_open_pull_requests "5" read as 5declare at least one of limit, pull_request_creation_cap, ...expirywithoutlimitexpiry rides the base interaction-limits PUT, which requires a limitGitHub caps the bypass list at 100 usershealed: pull_request_creation_bypass lists "octocat" twice; kept oneffftoffffff(names stay refused)healed: color "#fff" written as "ffffff"a label description is at most 100 charactersthis is a private key; a deploy key takes the public halfPUBLIC KEYblock to its OpenSSH line (other PEM kinds stay refused)healed: entry "ci": PEM public key written in OpenSSH formhealed: entry "ci": trailing line break removedhealed: entry "ci": non-breaking space read as a spacethe key has fewer than two fieldsssh-dssthe key is DSA, which GitHub no longer acceptsthe key's first field is not an algorithm GitHub acceptsthe key's second field is not base64the entries "a" and "b" declare the same key materialvalue: []null(an empty set unsets)healed: "team" value [] written as nullhealed: "team" lists "core" twice; kept onenameandvaluedeployment_branch_policy sets both ... to truedeployment_branch_policy: nullhealed: deployment_branch_policy with both flags false written as nullhealed: wait_timer "30" read as 30wait_timer cannot be negativeorGitHub caps wait_timer at 43200 minutesGitHub allows at most 6 required reviewerstypeoff User or Team,idnot a numbertype, quoted integeridhealed: reviewers[0].type "team" read as "Team"healed: type "Branch" read as "branch"secretkey{name, value}intosecretswhensecretsis absenthealed: secret moved into secrets[0]truewithout reviewersprevent_self_review: true without reviewerscustom_branch_policies: truedeployment_branch_policy: {protected_branches: false, custom_branch_policies: true}when absent (a declared conflict stays refused)healed: deployment_branch_policy added with custom_branch_policies: trueGitHub allows at most 10 pinned environmentsUnrecognized keydue_on is a calendar day, YYYY-MM-DDhealed: state "Open" read as "open"url,html_url,status, and the other read-only fieldsGitHub reports this field on the Pages site and the update has no such parameterhealed: build_type "Workflow" read as "workflow"/or/docsdocs,docs/,/docs/to/docshealed: source.path "docs" read as "/docs"id,full_name, the*_countkeys, and the rest<key> is reported by GitHub but cannot be set through the API; remove ithas_pages,custom_propertiesdeclare it in the <section> section instead"true"totrue(yes, no, on, off stay refused)healed: has_wiki "true" read as truedescription,homepage,visibility,default_branch<value> is not a string; quote the valuehealed: status "Enabled" read as "enabled""k" is not a key a security_and_analysis feature acceptsdependabot_security_updatesenable_automated_security_fixes(enabled to true) when that key is absenthealed: dependabot_security_updates moved to enable_automated_security_fixesreviewer_type,modeoff set,reviewer_idnot an integerhealed: reviewer_type "team" read as "TEAM"healed: squash_merge_commit_title "pr_title" read as "PR_TITLE"<message key> needs <title key> declared beside it<title> cannot pair with <message> (GitHub answers 422)healed: topic " ci" trimmed to "ci"""stays refused)healed: an empty topic droppeda,,b, a trailing comma)healed: an empty comma segment droppedN topics declared; GitHub allows at most 20healed: issue_creation_policy "All" read as "all"~outside~ALL,~DEFAULT_BRANCHhealed: ref_name "~all" read as "~ALL"healed: ref_name " main" trimmedactor_ida Team bypass actor needs its numeric actor_idactor_idon a DeployKeya DeployKey bypass actor takes no actor_idbypass_mode: pull_requeston a DeployKeybypass_mode "pull_request" does not apply to a DeployKey actoractor_type,bypass_modeoff set,actor_idnot an integerhealed: actor_type "team" read as "Team"pull_requestbypass on a tag or push rulesetbypass_mode "pull_request" applies to branch rulesets onlyenabledstays refused)healed: enforcement "Active" read as "active"healed: merge_method "squash" read as "SQUASH"healed: max_file_size "10" read as 10allowed_merge_methods: []allowed_merge_methods needs at least one of "merge", "squash", "rebase"typehealed: ruleset "main" lists rule type "deletion" twice; kept onecannot be compiled as a regular expression (<reason>)a delimiter cannot be cleared with an empty stringstateandpush_protection_enabledincludedhealed: team " core" trimmed to "core"healed: event "Push" read as "push"healed: config.url trimmedapplication/jsontojson,application/x-www-form-urlencodedtoformhealed: content_type "application/json" read as "json""0"or"1"use "0" (verify the TLS certificate) or "1" (skip verification)webexpected "web"secretbesideconfigconfig.secretwhen that is absenthealed: secret moved to config.secrethealed: state "Active" read as "active"Counts: 218 rows, 76 yes, 3 in #462, 139 no.
Test census (the files pinning each yes row today, to flip to heal plus note)
test/engine/orchestrate.test.ts,test/engine/layers.test.ts,test/flows/layers.test.ts,test/problem.test.ts.test/flows/inputs.test.ts,test/action/run.test.ts,test/problem.test.ts.test/discovery/repos-input.test.ts,test/problem.test.ts.test/cli/inputs.test.ts.test/sections/list-wrapper-schema.test.ts,test/sections/secret-variable-schema.test.ts.test/sections/setup-section.test.ts(row 81 has no pin).test/sections/roles.test.ts.test/sections/validate-hook.test.ts,test/sections/list-section.test.ts,test/sections/refusal-messages.test.ts,test/engine/validate.test.ts, and each section's own test (labels, autolinks, branches, collaborators, actions_secrets, actions_variables, environments, teams, webhooks, workflows, secret_scanning_custom_patterns).test/sections/actions/schema.test.ts,test/sections/actions/actions.test.ts.test/sections/autolinks/autolinks.test.ts.test/sections/branches/schema.test.ts.test/sections/check_suite_preferences/schema.test.ts.test/sections/interaction_limits/schema.test.ts.test/sections/labels/schema.test.ts.test/sections/deploy_keys/schema.test.ts.test/sections/custom_properties/schema.test.ts,test/sections/custom_properties/custom_properties.test.ts.test/sections/environments/schema.test.ts,test/fixtures/environment-parse-rules.ts,test/engine/validate.test.ts,test/sections/raw-sibling.test.ts.milestones-invalid-state-rejected.ymlonly.test/sections/pages/pages.test.ts.test/sections/repository/schema.test.ts,test/sections/repository/repository.test.ts,test/published-schema.test.ts.test/sections/rulesets/schema.test.ts,test/sections/rulesets/rulesets.test.ts,test/engine/layers.test.ts.test/sections/teams/schema.test.ts.test/sections/webhooks/schema.test.ts.test/engine/validate.test.ts.*-name-rejected,rulesets-known-wrong-values-rejected,webhooks-known-wrong-values-rejected,branches-restrictions-missing-lists-rejected, andlabels-duplicate-rejected-before-any-writefamilies.Files:
AGENTS.md.