Skip to content

docs(agents): heal an unambiguous malformation at the boundary and refuse the rest - #464

Merged
Vivswan merged 2 commits into
mainfrom
docs/healing-rule
Oct 5, 2026
Merged

Vivswan merged 2 commits into
mainfrom
docs/healing-rule

Conversation

@Vivswan

@Vivswan Vivswan commented Oct 5, 2026

Copy link
Copy Markdown
Owner

What this changes

before:
- What can be known wrong from the settings file alone is refused when the file is parsed, naming the key and the fix,
  never discovered at apply time: GET-only fields, enum violations, contradictory key pairs, unknown keys in a closed
  GitHub shape. Open passthrough shapes keep unknown keys and note them at check time when GitHub does not echo them back.

after:
- 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.
  One with no single repair is refused there, naming the key and the fix, never discovered at apply time:
  GET-only fields, other enum violations, contradictory key pairs, and unknown keys in a closed GitHub shape have none,
  since dropping or guessing a value discards what the author wrote.
  Open passthrough shapes keep unknown keys and note them at check time when GitHub does not echo them back.
parse-time refusals audited   218
  heal (one repair)            76   six classes, listed under Technical details
  in #462                       3   discovery filters
  refuse (no single repair)   139

How

  • Heal first: a refusal with exactly one repair becomes a heal plus a run-output note.
  • Deleting is not a repair: a GET-only key or a no-op key stays refused, since dropping it discards the author's value.
  • No code moves here: the table under Technical details sorts every parse-time refusal under src/.

Proof

  • Typography: the bullet is ASCII only, no line over 191 characters.
  • Census tests: the 35 test files named below pass unchanged, 986 tests.
Technical details

Heal classes (each yes row carries one tag)

H1 trim     surrounding whitespace on a scalar the grammar refuses for it
H2 case     a closed-vocabulary value equal to exactly one member up to case
H3 bool     a quoted "true" or "false" where a boolean is required (yes/no/on/off stay refused)
H4 int      a quoted integer string inside the field's range
H5 dup      a repeat identical to an earlier entry, or a set item equal under GitHub's own case fold: keep the first
H6 respell  one documented one-to-one rewrite named in the refusal text

Rulings

  • Refused: deleting a GET-only, read-only, or no-op key. The author wrote a value, and dropping it discards that value.
  • Refused: the pre-v3 undeclared wrapper key. Healing it is a compatibility alias, which the no-shims hard rule and docs/reference/undeclared-policy.md both forbid.
  • Refused: settings-file: only.yml,. A path may contain a comma, so the stray separator has two readings.
  • Refused: yes, no, on, off as booleans. The refusal text already calls them ambiguous.
  • Refused: clamping an out-of-range number. The intended value is unknown.
  • Already healed silently, no note: inputs() trims every input, affiliation and the sections lists drop repeats, and topics lowercases.

The audit (paths under src/, one row per refusal)

# Where Refuses Repair The repair Note a user sees
1 engine/orchestrate.ts validateSettingsDoc settings-not-mapping a top level that is not a mapping no <source> must be a YAML mapping of section names to settings, but its top level parsed as a <shape>
2 engine/orchestrate.ts settings-not-plain-mapping a YAML-tagged top level (!!timestamp) no <source> must be a plain YAML mapping of section names to settings
3 engine/orchestrate.ts unknownDirectivesIssue an unknown _x top-level key no unknown underscore key: _x. The underscore marks this action's directives
4 engine/orchestrate.ts badDirectiveIssue _undeclared outside keep/delete yes H2 Keep to keep healed: _undeclared "Keep" read as "keep"
5 engine/orchestrate.ts unknownSectionsIssue an unknown top-level section no unknown top-level section: x (known: ...). Fix the typo
6 engine/orchestrate.ts singleDocumentRemovalIssue _remove: true in a single document no <site>: a single document has no lower layer to remove from
7 engine/validate.ts validateSectionShapes null section section: null where the section has no off state no <key>: null has no meaning; remove the section or declare its entries
8 engine/validate.ts nonPlainOffence a tagged value, a hole, a symbol key, a list subclass no <path> is not plain YAML data (<what>); replace it with a plain value
9 engine/validate.ts findOffending cycle a YAML alias cycle in a section no <path> refers back to one of its own containers (a YAML alias cycle)
10 engine/validate.ts nonFiniteOffence .nan or .inf in a passthrough field no <path> is NaN, which JSON cannot carry
11 engine/validate.ts null rewrite null where the key has no empty state no <path> has no empty state; write <legal values>
12 engine/validate.ts closedSurfaceProblems an unknown key in a closed list entry no <key>[i] (<field> "x"): declares "k", which this section does not recognize
13 engine/secret-refs.ts validateSecretRef literal a literal secret value no <label> carries a literal value, but settings files are committed plaintext
14 engine/secret-refs.ts validateSecretRef embedded prefix-$TOKEN no <label> embeds $TOKEN without being a whole-value reference
15 engine/secret-refs.ts validateSecretRef reserved $INPUT_*, $GITHUB_*, and the other reserved prefixes no <label> references the reserved runner variable $X
16 engine/secret-refs.ts validateSecretRef target a reference in a target-fetched file no <label> uses the secret reference $X in a target-fetched settings file
17 flows/settings-read.ts parseSettingsDoc yaml-invalid a YAML syntax error no the parser's own message
18 flows/settings-read.ts readSettingsFile settings-file-unreadable a missing or unreadable file no cannot read settings from <path>: <reason>
19 engine/admit.ts admit layer-cycle a cyclic layer no layer "x": the document contains a reference cycle
20 engine/admit.ts admitEntries layer-wrong-shape a list entry that is not a mapping no layer "x": <site> must be a mapping; got <kind>
21 engine/admit.ts admitSection layer-wrong-shape a list section neither list nor wrapper no layer "x": <key> must be a list of mappings or an {_layering, entries} wrapper
22 engine/admit.ts fileLayering, admitSection, fileUndeclared layer-bad-directive _layering or _undeclared outside its set yes H2 Deep to deep healed: layer "x": _layering "Deep" read as "deep"
23 engine/admit.ts checkRemoval layer-remove-not-true _remove not true yes H3 "true" to true (other values stay refused) healed: layer "x": <site>._remove "true" read as true
24 engine/admit.ts checkRemoval layer-remove-with-fields _remove: true beside other fields no layer "x": <site> carries _remove: true beside "color"
25 engine/admit.ts checkRemoval layer-remove-nothing replace _remove under _layering: replace no layer "x": <site> carries _remove: true, but under _layering: replace ...
26 engine/admit.ts checkKeyed layer-no-key an entry without its key field no layer "x": <site> carries no string "name", which every entry needs to layer by
27 engine/admit.ts checkKeyed layer-duplicate-key two entries of one layer claiming one key yes H5 collapse when the two entries are deep-equal (differing entries stay refused) healed: layer "x": labels[2] repeats labels[0] exactly; kept one
28 engine/layers.ts refuseRemovalsIn layer-remove-nothing swapped, unmatched a removal with nothing below it no layer "x": <site> carries _remove: true, but no lower layer declares an entry under its key
29 engine/section-selection.ts SectionSelection.of a required section outside the sections allowlist no the "required-sections" entry "x" is excluded by the "sections" allowlist
30 flows/input-readers.ts readEnum input-unsupported-value mode, on-missing-permission, private-repos, private-report, layering, undeclared off the set yes H2 Apply to apply healed: the "mode" input "Apply" read as "apply"
31 flows/input-readers.ts readEnum visibility, archived, forks a discovery filter off the set in #462
32 flows/input-readers.ts readDiscoveryFilters input-affiliation-unsupported an unknown affiliation in #462
33 flows/input-readers.ts readDiscoveryFilters input-exclude-pattern-invalid an unmatchable exclude pattern in #462
34 cli/program.ts commander parse an unknown flag, or a flag missing its value no commander's own error: unknown option '--x'
35 flows/input-readers.ts readSectionSelection input-unknown-sections an unknown name in sections or required-sections no unknown section "x" in the "sections" input
36 flows/input-readers.ts resolveReportPublicKey input-report-key-unused a key with a non-artifact channel no the "report-public-key" input only applies to private-report: artifact
37 flows/input-readers.ts resolveReportPublicKey input-report-key-missing artifact channel without a key no private-report: artifact needs a "report-public-key" input
38 flows/input-readers.ts resolveReportPublicKey input-report-key-invalid a malformed age recipient no the "report-public-key" input is not a valid age recipient
39 flows/input-readers.ts readToken input-token-missing no token no cannot call the GitHub API: no token was provided
40 flows/input-readers.ts readSingleTarget input-repository-not-slug a repository that is no slug no cannot target a repository: "x" is not an owner/name slug
41 flows/run-config.ts parseRenderConfig input-rejected-in-render an apply input set in render no the "x" input does not apply to mode: render
42 flows/run-config.ts parseRenderConfig input-rendered-file-missing render without rendered-file no mode: render needs a "rendered-file" input
43 flows/run-config.ts parseRenderConfig input-settings-file-empty render with no layer no the "settings-file" input is "", which lists no file
44 flows/run-config.ts parseSnapshotConfig input-rejected-in-snapshot an apply input set in snapshot no the "x" input does not apply to mode: snapshot
45 flows/run-config.ts parseSnapshotConfig input-snapshot-destinations-both snapshot-file and snapshot-dir both set no the "snapshot-file" and "snapshot-dir" inputs are both set
46 flows/run-config.ts parseSnapshotConfig input-snapshot-destination-missing neither destination no mode: snapshot needs exactly one of the "snapshot-file" input ... or the "snapshot-dir" input
47 flows/run-config.ts parseSnapshotFileArm input-snapshot-file-with-multi snapshot-file with repos no the "snapshot-file" input writes one repository's snapshot, but "repos" ... names multi-repo targets
48 flows/run-config.ts parseSnapshotConfig input-repository-with-snapshot-dir repository with snapshot-dir no the "repository" input cannot be combined with "snapshot-dir"
49 flows/run-config.ts parseSnapshotConfig input-snapshot-dir-without-targets snapshot-dir with no targets no the "snapshot-dir" input needs multi-repo targets
50 flows/run-config.ts parseConfig input-render-only a render input in apply or check no the "layering" input only applies to mode: render
51 flows/run-config.ts parseConfig input-snapshot-only a snapshot input in apply or check no the "snapshot-file" input only applies to mode: snapshot
52 flows/run-config.ts parseConfig input-artifact-unsupported artifact channel on a face with no upload no private-report: artifact uploads the reports as a workflow artifact
53 flows/run-config.ts parseConfig input-report-without-redaction a report channel with private-repos: show no the "private-report" input delivers reports only for redacted targets
54 flows/run-config.ts parseConfig input-repository-with-multi repository with repos no the "repository" input cannot be combined with "repos" or "repos-dir"
55 flows/run-config.ts parseConfig input-settings-file-with-multi settings-file with repos no the "settings-file" input cannot be combined with "repos" or "repos-dir"
56 flows/run-config.ts parseConfig discovery-filters-without-wildcard a filter without repos: "*" no the discovery filter input "visibility" only applies to repos: "*" discovery
57 flows/run-config.ts parseConfig input-defaults-file-without-multi defaults-file in single-repo mode no the "defaults-file" input only applies to multi-repo mode
58 flows/run-config.ts parseConfig, parseSnapshotFileConfig input-settings-file-is-list a list separator in a one-file mode no the "settings-file" input is "x", which contains a list separator
59 discovery/repos-input.ts parseReposInput repos-input-wildcard-mixed * beside explicit slugs no the "repos" input mixes "*" with explicit repositories
60 discovery/repos-input.ts parseReposInput invalid an entry that is no slug no the "repos" input has 1 invalid entry: "x" is not an owner/name slug
61 discovery/repos-input.ts parseReposInput duplicated one slug listed twice, any case yes H5 keep the first healed: the "repos" input lists "o/n" twice; kept one
62 discovery/targets.ts parseRepoSlug repo-slug-invalid a slug failing SLUG_RE no "x" is not an owner/name repository slug
63 discovery/central.ts resolveCentralTargets repos-dir-missing a missing repos-dir no repos-dir "x" does not exist in the workspace
64 discovery/central.ts resolveCentralTargets repos-dir-unreadable an unreadable repos-dir no cannot read repos-dir "x"
65 discovery/central.ts addTarget not-a-slug a file name that is no slug no <file> resolves to the target "x", which is not a valid owner/name slug
66 discovery/central.ts addTarget duplicate two files for one slug no duplicate target o/n: defined by both <a> and <b>
67 discovery/central.ts ownerless top-level files with no known owner no cannot resolve <files>: top-level repos-dir files use the current repository's owner
68 cli/inputs.ts once a single-value flag repeated yes H5 collapse identical values (differing values stay refused) healed: --mode given twice as "apply"; read once
69 cli/init.ts init-settings-file-exists init over an existing file without --force no the refusal naming --force
70 sections/shared/schema-helpers.ts wrapperKeyError renamed the pre-v3 undeclared wrapper key no the wrapper's policy key "undeclared" was renamed to "_undeclared" in v3
71 sections/shared/schema-helpers.ts wrapperKeyError directives any other _x wrapper key no the wrapper's directives are "_undeclared" and ... nothing else
72 sections/shared/schema-helpers.ts wrapper strictObject an unknown non-underscore wrapper key no zod: Unrecognized key: "entrys"
73 sections/shared/schema-helpers.ts routedForms a list section that is neither list nor mapping no Invalid input: expected a list of entries, or a mapping with "entries"
74 sections/shared/schema-helpers.ts UndeclaredPolicySchema, LayeringSchema a wrapper _undeclared or _layering off its set yes H2 Delete to delete healed: labels._undeclared "Delete" read as "delete"
75 sections/shared/schema-helpers.ts variableValue a variable over 48 KB no the variable value is N bytes of UTF-8; GitHub caps a variable at 48 KB
76 sections/shared/schema-helpers.ts githubName empty, digit, GITHUB_ an empty name, a leading digit, the reserved prefix no the secret name "x" starts with a digit - GitHub accepts ...
77 sections/shared/schema-helpers.ts githubName characters characters outside [A-Za-z0-9_] yes H1 trim when surrounding whitespace is the only fault (inner - stays refused) healed: secret name " TOKEN" trimmed to "TOKEN"
78 sections/shared/schema-helpers.ts sealedSecretConfig value a non-string secret value no zod type message
79 sections/shared/setup-schema.ts languagesSchema GET spelling javascript or typescript yes H6 javascript-typescript, collapsed when both appear healed: languages "javascript" written as "javascript-typescript"
80 sections/shared/setup-schema.ts languagesSchema GET-only rust in code quality no "rust" is reported by GitHub but the PATCH cannot set it; remove it
81 sections/shared/setup-schema.ts languagesSchema other an unknown language yes H2 case-fold to one member only healed: languages "Python" read as "python"
82 sections/shared/setup-schema.ts refineSetup GET_ONLY_KEYS schedule, updated_at no "schedule" is reported by GitHub but the PATCH does not accept it
83 sections/shared/setup-schema.ts refineSetup labeled runner_type: labeled without runner_label no runner_type: "labeled" needs a runner_label
84 sections/shared/setup-schema.ts refineSetup stray label runner_label without runner_type: labeled no runner_label "x" is declared ..., where GitHub ignores it
85 sections/shared/roles.ts permissionError empty an empty or whitespace-only permission no an empty permission grants nothing
86 sections/shared/roles.ts permissionError multi-line a permission over several lines no "x" spans several lines; a permission is one line
87 sections/shared/roles.ts permissionError end whitespace "push\n" yes H1 trim healed: permission "push\n" trimmed to "push"
88 sections/shared/roles.ts permissionError GET vocabulary read, write, any case yes H6 read to pull, write to push healed: permission "write" written as "push"
89 sections/shared/roles.ts permissionError case Push, ADMIN yes H2 lowercase healed: permission "ADMIN" read as "admin"
90 sections/contract/module.ts duplicateIssues, duplicateFieldIssues (every list section and nested list) two entries with one identity yes H5 collapse when deep-equal (differing entries stay refused) healed: labels[2] repeats labels[0] exactly; kept one
91 sections/contract/module.ts requirePlainMapping a tagged value where a mapping is expected no Invalid input: expected a plain mapping
92 sections/actions/schema.ts refuseReportedOnly selected_actions_url, maximum_allowed_days, sub_claim_prefix no <field> is a value GitHub reports, not a setting it accepts
93 sections/actions/schema.ts ActionsConfig rule selected_actions without allowed_actions: selected no selected_actions is declared together with allowed_actions: ...
94 sections/actions/schema.ts ClaimKeys regex a claim key outside [A-Za-z0-9_] no a claim key holds only letters, digits, and underscores
95 sections/actions/schema.ts ClaimKeys rule a repeated claim key yes H5 drop an adjacent repeat (a distant one stays refused, since order shapes the sub) healed: include_claim_keys repeats "repo" adjacently; kept one
96 sections/actions/schema.ts OidcTemplate rule include_claim_keys under use_default: true no GitHub ignores include_claim_keys under use_default: true
97 sections/actions/schema.ts OidcTemplate discriminator use_default missing or not a boolean yes H3 "true" to true (missing stays refused) healed: use_default "true" read as true
98 sections/actions/schema.ts enums allowed_actions, default_workflow_permissions, access_level, approval_policy yes H2 case-fold healed: allowed_actions "All" read as "all"
99 sections/actions/schema.ts selected_actions strictObject an unknown key no zod: Unrecognized key
100 sections/actions/schema.ts cache strictObject an unknown key no zod: Unrecognized key
101 sections/actions/schema.ts ints retention days, cache days, cache size yes H4 quoted in-range integer to number (zero, negative, fraction stay refused) healed: cache.max_cache_size_gb "10" read as 10
102 sections/actions/schema.ts fork_pr_workflows_private_repos run_workflows_from_fork_pull_requests missing no zod required message
103 sections/autolinks/schema.ts key_prefix empty "" no key_prefix is empty
104 sections/autolinks/schema.ts key_prefix characters characters outside GitHub's set yes H1 trim when surrounding whitespace is the only fault healed: key_prefix " TICKET-" trimmed
105 sections/autolinks/schema.ts url_template no <num> placeholder no url_template "x" has no "<num>" placeholder
106 sections/autolinks/index.ts conflicts one prefix beginning another no the key_prefix "TICK" begins the key_prefix "TICKET-"
107 sections/branches/schema.ts parseBypassActor a bad force_push_bypassers actor yes H6 strip a leading @ on an otherwise valid login (others stay refused) healed: force_push_bypassers "@octocat" read as "octocat"
108 sections/branches/schema.ts actorList copied object a GET actor object {login} in an actor list yes H6 the object's login or slug string healed: restrictions.users[0] written as "octocat"
109 sections/branches/schema.ts actorList other item a non-string actor no <site> lists each actor as its login string
110 sections/branches/schema.ts actorList missing list restrictions without users or teams yes H6 add the missing list as [] healed: protection.restrictions.teams added as []
111 sections/branches/schema.ts actorList not a list an actor list that is no list no <site> lists each actor as its login string
112 sections/branches/schema.ts holderError an actor holder that is no mapping no protection.restrictions must be a mapping of users, teams, and apps lists
113 sections/branches/schema.ts RequiredStatusChecks.strict strict missing or not a boolean yes H3 "true" to true (missing stays refused) healed: strict "true" read as true
114 sections/branches/schema.ts RequiredStatusChecks rule neither contexts nor checks no required_status_checks must list the required checks
115 sections/branches/schema.ts RequiredStatusChecks holder not a mapping no required_status_checks must be a mapping of its keys
116 sections/branches/schema.ts RequiredStatusCheck item a bare string in checks no each required_status_checks.checks item is a {context, app_id} mapping
117 sections/branches/schema.ts RequiredStatusCheck strictObject an unknown key in a check no a required_status_checks.checks item takes only context and app_id
118 sections/branches/schema.ts RequiredStatusCheck.context a non-string context no required_status_checks.checks[].context must be the check's name
119 sections/branches/schema.ts RequiredStatusCheck.app_id a non-integer app_id yes H4 quoted integer to number healed: app_id "15368" read as 15368
120 sections/branches/schema.ts required_approving_review_count outside 0 to 6 or not an integer yes H4 quoted integer in 0 to 6 to number healed: required_approving_review_count "2" read as 2
121 sections/branches/schema.ts RequiredPullRequestReviews holder not a mapping no required_pull_request_reviews must be a mapping of its keys
122 sections/branches/schema.ts getOnlyKeyError enabled wrapper enforce_admins: {enabled: true} yes H6 {enabled: v} to v healed: enforce_admins {enabled: true} written as true
123 sections/branches/schema.ts getOnlyKeyError url a url or *_url key no <site> is a link GitHub's GET response carries
124 sections/branches/schema.ts getOnlyKeyError echo name, enforcement_level, a stray enabled no <site> is GitHub's GET-only echo
125 sections/branches/schema.ts required_signatures not a boolean yes H3 "true" to true healed: required_signatures "true" read as true
126 sections/branches/schema.ts required_deployments strictObject a key beside environments no zod: Unrecognized key
127 sections/branches/schema.ts BranchConfig rule bypassers a repeated bypasser, any case yes H5 drop the later repeat healed: force_push_bypassers lists "octocat" twice; kept one
128 sections/branches/schema.ts BranchConfig rule deployments a repeated environment, any case yes H5 drop the later repeat healed: required_deployments.environments lists "prod" twice; kept one
129 sections/branches/schema.ts WILDCARD_KEY_ERROR an unmanaged key on a wildcard entry no the wildcard entry "x" declares protection.<key>, which this section does not manage
130 sections/check_suite_preferences/schema.ts AppId a non-positive or non-integer app_id yes H4 quoted positive integer to number healed: app_id "15368" read as 15368
131 sections/check_suite_preferences/schema.ts rule a repeated app_id yes H5 collapse when both carry one setting (conflicting settings stay refused) healed: auto_trigger_checks[1] repeats [0] exactly; kept one
132 sections/check_suite_preferences/schema.ts setting setting missing or not a boolean yes H3 "true" to true (missing stays refused) healed: setting "false" read as false
133 sections/code_scanning_default_setup/schema.ts enums state, query_suite, runner_type, threat_model yes H2 case-fold healed: state "Configured" read as "configured"
134 sections/code_quality_setup/schema.ts enums state, runner_type, ai_findings_option yes H2 case-fold healed: state "Configured" read as "configured"
135 sections/interaction_limits/schema.ts strictObject an unknown key, origin and expires_at included no interaction_limits takes limit, expiry, ...; remove the key, or fix its spelling
136 sections/interaction_limits/schema.ts limit, expiry off GitHub's sets yes H2 case-fold healed: limit "Existing_Users" read as "existing_users"
137 sections/interaction_limits/schema.ts cap.enabled not a boolean yes H3 "true" to true healed: enabled "true" read as true
138 sections/interaction_limits/schema.ts max_open_pull_requests outside 1 to 1000 yes H4 quoted in-range integer to number healed: max_open_pull_requests "5" read as 5
139 sections/interaction_limits/schema.ts rule empty nothing declared no declare at least one of limit, pull_request_creation_cap, ...
140 sections/interaction_limits/schema.ts rule expiry expiry without limit no expiry rides the base interaction-limits PUT, which requires a limit
141 sections/interaction_limits/schema.ts rule bypass cap over 100 bypass logins no GitHub caps the bypass list at 100 users
142 sections/interaction_limits/schema.ts rule bypass repeat one login twice, any case yes H5 drop the later repeat healed: pull_request_creation_bypass lists "octocat" twice; kept one
143 sections/labels/schema.ts LabelColor a color that is not six hex digits yes H1, H6 trim, and expand three-digit shorthand fff to ffffff (names stay refused) healed: color "#fff" written as "ffffff"
144 sections/labels/schema.ts LabelDescription over 100 characters no a label description is at most 100 characters
145 sections/deploy_keys/schema.ts private key a PEM private key no this is a private key; a deploy key takes the public half
146 sections/deploy_keys/schema.ts PEM block a PEM block yes H6 convert a PUBLIC KEY block to its OpenSSH line (other PEM kinds stay refused) healed: entry "ci": PEM public key written in OpenSSH form
147 sections/deploy_keys/schema.ts line break a line break in the key yes H1 strip a trailing break only (inner breaks stay refused) healed: entry "ci": trailing line break removed
148 sections/deploy_keys/schema.ts odd whitespace a non-breaking space between fields yes H1 replace with a space healed: entry "ci": non-breaking space read as a space
149 sections/deploy_keys/schema.ts too few fields under two fields no the key has fewer than two fields
150 sections/deploy_keys/schema.ts DSA ssh-dss no the key is DSA, which GitHub no longer accepts
151 sections/deploy_keys/schema.ts algorithm an unknown algorithm no the key's first field is not an algorithm GitHub accepts
152 sections/deploy_keys/schema.ts base64 a blob that is no base64 no the key's second field is not base64
153 sections/deploy_keys/index.ts conflicts two titles, one key no the entries "a" and "b" declare the same key material
154 sections/custom_properties/index.ts empty list value: [] yes H6 null (an empty set unsets) healed: "team" value [] written as null
155 sections/custom_properties/index.ts repeated element a multi_select value listed twice yes H5 drop the repeat healed: "team" lists "core" twice; kept one
156 sections/custom_properties/index.ts closedSurface an unknown entry key no row 12's text
157 sections/custom_properties/schema.ts value a value outside the union no zod union message
158 sections/collaborators/index.ts closedSurface an unknown entry key no row 12's text, naming the default role
159 sections/codespaces_secrets, dependabot_secrets, actions_secrets, agents_secrets via shared/repo-secrets.ts closedSurface a key beside name and value no row 12's text
160 sections/environments/schema.ts DeploymentBranchPolicyFlags both true both flags true no deployment_branch_policy sets both ... to true
161 sections/environments/schema.ts DeploymentBranchPolicyFlags both false both flags false yes H6 deployment_branch_policy: null healed: deployment_branch_policy with both flags false written as null
162 sections/environments/schema.ts wait_timer type not an integer yes H4 quoted integer to number (fraction stays refused) healed: wait_timer "30" read as 30
163 sections/environments/schema.ts wait_timer range negative or over 43200 no wait_timer cannot be negative or GitHub caps wait_timer at 43200 minutes
164 sections/environments/schema.ts reviewers count over 6 reviewers no GitHub allows at most 6 required reviewers
165 sections/environments/schema.ts reviewers item type off User or Team, id not a number yes H2, H4 case-fold type, quoted integer id healed: reviewers[0].type "team" read as "Team"
166 sections/environments/schema.ts deployment_branch_policies type off branch or tag yes H2 case-fold healed: type "Branch" read as "branch"
167 sections/environments/schema.ts EnvironmentConfig singular secret a secret key yes H6 wrap one {name, value} into secrets when secrets is absent healed: secret moved into secrets[0]
168 sections/environments/schema.ts EnvironmentConfig prevent_self_review true without reviewers no prevent_self_review: true without reviewers
169 sections/environments/schema.ts EnvironmentConfig branch policies patterns without custom_branch_policies: true yes H6 add deployment_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: true
170 sections/environments/schema.ts EnvironmentsConfig pins over 10 pinned no GitHub allows at most 10 pinned environments
171 sections/environments/schema.ts nested strictObjects an unknown key in a secret or protection rule no zod: Unrecognized key
172 sections/milestones/schema.ts DueOn a non-ISO date no due_on is a calendar day, YYYY-MM-DD
173 sections/milestones/schema.ts state off open or closed yes H2 case-fold healed: state "Open" read as "open"
174 sections/pages/schema.ts READ_ONLY_SITE_FIELDS url, html_url, status, and the other read-only fields no GitHub reports this field on the Pages site and the update has no such parameter
175 sections/pages/schema.ts build_type off workflow or legacy yes H2 case-fold healed: build_type "Workflow" read as "workflow"
176 sections/pages/schema.ts source.path off / or /docs yes H6 docs, docs/, /docs/ to /docs healed: source.path "docs" read as "/docs"
177 sections/repository/schema.ts GET_ONLY_KEYS id, full_name, the *_count keys, and the rest no <key> is reported by GitHub but cannot be set through the API; remove it
178 sections/repository/schema.ts SECTION_OWNED_KEYS has_pages, custom_properties no declare it in the <section> section instead
179 sections/repository/schema.ts repositoryToggle a non-boolean toggle yes H3 "true" to true (yes, no, on, off stay refused) healed: has_wiki "true" read as true
180 sections/repository/schema.ts patchString a non-string description, homepage, visibility, default_branch no <value> is not a string; quote the value
181 sections/repository/schema.ts FeatureStatus a status off enabled or disabled yes H2 case-fold (a boolean stays refused) healed: status "Enabled" read as "enabled"
182 sections/repository/schema.ts FeatureToggle, bypass reviewer, bypass options, SecurityAndAnalysisConfig strictObjects an unknown key no "k" is not a key a security_and_analysis feature accepts
183 sections/repository/schema.ts securityAndAnalysisHint dependabot_security_updates yes H6 move to enable_automated_security_fixes (enabled to true) when that key is absent healed: dependabot_security_updates moved to enable_automated_security_fixes
184 sections/repository/schema.ts bypassReviewerShape reviewer_type, mode off set, reviewer_id not an integer yes H2, H4 case-fold, quoted integer healed: reviewer_type "team" read as "TEAM"
185 sections/repository/schema.ts commitMessageFamily enums a merge or squash title or message off set yes H2 case-fold healed: squash_merge_commit_title "pr_title" read as "PR_TITLE"
186 sections/repository/schema.ts refineCommitMessagePairs missing a message without its title no <message key> needs <title key> declared beside it
187 sections/repository/schema.ts refineCommitMessagePairs illegal an illegal title and message pair no <title> cannot pair with <message> (GitHub answers 422)
188 sections/repository/schema.ts topicName a topic off the grammar yes H1 trim when surrounding whitespace is the only fault healed: topic " ci" trimmed to "ci"
189 sections/repository/schema.ts topicName empty an empty topic yes H6 drop it when other topics remain (a lone "" stays refused) healed: an empty topic dropped
190 sections/repository/schema.ts topicList a comma segment off the grammar yes H6 drop empty segments (a,,b, a trailing comma) healed: an empty comma segment dropped
191 sections/repository/schema.ts refineTopicCount over 20 topics no N topics declared; GitHub allows at most 20
192 sections/repository/schema.ts creationPolicy off all or collaborators_only yes H2 case-fold healed: issue_creation_policy "All" read as "all"
193 sections/rulesets/schema.ts RefNamePattern token ~ outside ~ALL, ~DEFAULT_BRANCH yes H2 case-fold to the token healed: ref_name "~all" read as "~ALL"
194 sections/rulesets/schema.ts RefNamePattern sequence a character git refuses yes H1 trim when surrounding whitespace is the only fault healed: ref_name " main" trimmed
195 sections/rulesets/schema.ts BypassActorConfig missing id an identified actor without actor_id no a Team bypass actor needs its numeric actor_id
196 sections/rulesets/schema.ts BypassActorConfig DeployKey id actor_id on a DeployKey no a DeployKey bypass actor takes no actor_id
197 sections/rulesets/schema.ts BypassActorConfig DeployKey mode bypass_mode: pull_request on a DeployKey no bypass_mode "pull_request" does not apply to a DeployKey actor
198 sections/rulesets/schema.ts BypassActorConfig fields actor_type, bypass_mode off set, actor_id not an integer yes H2, H4 case-fold, quoted integer healed: actor_type "team" read as "Team"
199 sections/rulesets/schema.ts RulesetConfig rule pull_request bypass on a tag or push ruleset no bypass_mode "pull_request" applies to branch rulesets only
200 sections/rulesets/schema.ts target, enforcement off set yes H2 case-fold (enabled stays refused) healed: enforcement "Active" read as "active"
201 sections/rulesets/schema.ts KNOWN_RULES enums a known rule's enum parameter off set yes H2 case-fold healed: merge_method "squash" read as "SQUASH"
202 sections/rulesets/schema.ts bounded ints a rule integer off range or not an integer yes H4 quoted in-range integer to number (out of range stays refused) healed: max_file_size "10" read as 10
203 sections/rulesets/schema.ts atLeastOneMergeMethod allowed_merge_methods: [] no allowed_merge_methods needs at least one of "merge", "squash", "rebase"
204 sections/rulesets/schema.ts required parameters a missing or mistyped parameter, a rule without type no zod required or type message
205 sections/rulesets/index.ts repeatedRuleTypes one rule type twice in a ruleset yes H5 collapse deep-equal rules (differing ones stay refused) healed: ruleset "main" lists rule type "deletion" twice; kept one
206 sections/secret_scanning_custom_patterns/schema.ts regexSource an expression that does not compile no cannot be compiled as a regular expression (<reason>)
207 sections/secret_scanning_custom_patterns/schema.ts delimiter an empty delimiter no a delimiter cannot be cleared with an empty string
208 sections/secret_scanning_custom_patterns/index.ts closedSurface an unknown key, state and push_protection_enabled included no row 12's text
209 sections/teams/schema.ts slugError a name that is no slug yes H1 trim when surrounding whitespace is the only fault (the slug guess stays a hint) healed: team " core" trimmed to "core"
210 sections/teams/index.ts closedSurface an unknown entry key no row 12's text, naming the default role
211 sections/webhooks/schema.ts WebhookEvent an event GitHub does not deliver yes H2 case-fold (plurals stay refused) healed: event "Push" read as "push"
212 sections/webhooks/schema.ts url a non-absolute URL yes H1 trim when surrounding whitespace is the only fault healed: config.url trimmed
213 sections/webhooks/schema.ts content_type off json or form yes H2, H6 case-fold, and application/json to json, application/x-www-form-urlencoded to form healed: content_type "application/json" read as "json"
214 sections/webhooks/schema.ts insecure_ssl off "0" or "1" no use "0" (verify the TLS certificate) or "1" (skip verification)
215 sections/webhooks/schema.ts name literal a name other than web no zod: expected "web"
216 sections/webhooks/schema.ts entry-level secret secret beside config yes H6 move to config.secret when that is absent healed: secret moved to config.secret
217 sections/workflows/schema.ts state off active or disabled yes H2 case-fold healed: state "Active" read as "active"
218 sections/workflows/index.ts closedSurface an unknown entry key no row 12's text

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)

  • Rows 4, 22, 23, 27: test/engine/orchestrate.test.ts, test/engine/layers.test.ts, test/flows/layers.test.ts, test/problem.test.ts.
  • Row 30: test/flows/inputs.test.ts, test/action/run.test.ts, test/problem.test.ts.
  • Row 61: test/discovery/repos-input.test.ts, test/problem.test.ts.
  • Row 68: test/cli/inputs.test.ts.
  • Rows 74, 77: test/sections/list-wrapper-schema.test.ts, test/sections/secret-variable-schema.test.ts.
  • Rows 79, 81: test/sections/setup-section.test.ts (row 81 has no pin).
  • Rows 87 to 89: test/sections/roles.test.ts.
  • Row 90: 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).
  • Rows 95, 98: test/sections/actions/schema.test.ts, test/sections/actions/actions.test.ts.
  • Rows 97, 101, 132, 133, 134, 166, 175: no test spells them today.
  • Row 104: test/sections/autolinks/autolinks.test.ts.
  • Rows 107, 108, 110, 113, 119, 120, 122, 125, 127, 128: test/sections/branches/schema.test.ts.
  • Rows 130, 131: test/sections/check_suite_preferences/schema.test.ts.
  • Rows 136 to 138, 142: test/sections/interaction_limits/schema.test.ts.
  • Row 143: test/sections/labels/schema.test.ts.
  • Rows 146 to 148: test/sections/deploy_keys/schema.test.ts.
  • Rows 154, 155: test/sections/custom_properties/schema.test.ts, test/sections/custom_properties/custom_properties.test.ts.
  • Rows 161, 162, 165, 167, 169: test/sections/environments/schema.test.ts, test/fixtures/environment-parse-rules.ts, test/engine/validate.test.ts, test/sections/raw-sibling.test.ts.
  • Row 173: the e2e scenario milestones-invalid-state-rejected.yml only.
  • Row 176: test/sections/pages/pages.test.ts.
  • Rows 179, 181, 183 to 185, 188 to 190, 192: test/sections/repository/schema.test.ts, test/sections/repository/repository.test.ts, test/published-schema.test.ts.
  • Rows 193, 194, 198, 200 to 202, 205: test/sections/rulesets/schema.test.ts, test/sections/rulesets/rulesets.test.ts, test/engine/layers.test.ts.
  • Row 209: test/sections/teams/schema.test.ts.
  • Rows 211 to 213, 216: test/sections/webhooks/schema.test.ts.
  • Row 217: test/engine/validate.test.ts.
  • E2e scenarios that assert a yes row's refusal also flip: the *-name-rejected, rulesets-known-wrong-values-rejected, webhooks-known-wrong-values-rejected, branches-restrictions-missing-lists-rejected, and labels-duplicate-rejected-before-any-write families.

Files: AGENTS.md.

…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.
Copilot AI balanced review requested due to automatic review settings October 5, 2026 06:05
@Vivswan
Vivswan enabled auto-merge (squash) October 5, 2026 06:14
@Vivswan
Vivswan disabled auto-merge October 5, 2026 06:14
@Vivswan
Vivswan merged commit b009ac0 into main Oct 5, 2026
13 checks passed
@Vivswan
Vivswan deleted the docs/healing-rule branch October 5, 2026 06:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The reporting rule conflicts with the documented silent normalization exception.

Review effort: Balanced
Findings: 1 Medium severity

Open (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 thread AGENTS.md
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.
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

File size check

0 over a hard cap (fails), 34 warning(s).

File Size Tier Cap
.github/actions/setup/action.yml:7 169 chars warn 150
.github/scripts/gen-inputs-table.ts:54 11 comment lines warn 10
.github/scripts/generated.ts:28 191 chars warn 150
.github/scripts/release-pipeline.ts:7 156 chars warn 150
.github/scripts/release-pipeline.ts:456 151 chars warn 150
.github/scripts/release-pipeline.ts:1250 153 chars warn 150
.github/scripts/release-pipeline.ts:1 33 comment lines (header) warn 25
.github/scripts/release-pipeline.ts:452 14 comment lines warn 10
.github/workflows/post-green.yml:29 153 chars warn 150
.github/workflows/update-release-pr.yml:110 14 comment lines warn 10
docs/upgrading/v2-to-v3.md 1276 lines warn 1040
src/engine/layers.ts:75 157 chars warn 150
src/engine/layers.ts:95 167 chars warn 150
src/flows/settings-write.ts:137 159 chars warn 150
src/github/api-error.ts:104 159 chars warn 150
src/github/secret-scan.ts:45 11 comment lines warn 10
src/schema.ts:188 153 chars warn 150
src/schema.ts:199 176 chars warn 150
src/sections/contract/errors.ts:13 14 comment lines warn 10
src/sections/secret_scanning_custom_patterns/compilable-form.ts:382 161 chars warn 150
src/sections/shared/roles.ts:43 13 comment lines warn 10
src/sections/shared/schema-helpers.ts:15 153 chars warn 150
src/types.ts:16 156 chars warn 150
test/docs/guides.test.ts:451 155 chars warn 150
test/docs/nightly-workflows.test.ts:206 160 chars warn 150
test/e2e/constants.ts:9 197 chars warn 150
test/e2e/generators.ts 2634 lines warn 2560
test/e2e/generators.ts:1704 166 chars warn 150
test/e2e/generators.ts:1858 161 chars warn 150
test/e2e/generators.ts:1667 12 comment lines warn 10
test/engine/execute.test.ts:617 152 chars warn 150
test/flows/merge-parity.test.ts:63 164 chars warn 150
test/scripts/auto-fix-allowlist.test.ts:12 12 comment lines warn 10
test/sections/refusal-messages.test.ts:61 19 comment lines warn 10

Split the file, wrap the line, shorten or exempt the comment, or list the path in .file-size-allow.local with a # reason.

5 managed file(s) skipped; repo-platform owns them.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants