For ruleset bypass_actors, GitHub's API ignores actor_id when actor_type is OrganizationAdmin (or DeployKey) and always returns actor_id: null for those entries. MergeDeep cannot handle that shape, in either direction:
Configured as actor_id: null (matching what the API returns): the entry never deploys. NAME_USERNAME_PROPERTY picks actor_id as the object's identity field because the key exists, GET_NAME_USERNAME_PROPERTY returns the null, and processArrays skips entries with a falsy id (if (id) visited[id] = a). An OrganizationAdmin actor present in config but missing from the live ruleset produces no addition, no modification, and a "no changes" plan. The sync silently does nothing.
Configured as actor_id: 1 (what docs/sample-settings/settings.yml shows): the entry deploys, but on the next sync the config entry (id 1) cannot match the live entry (id null), so the same change is detected and re-applied on every run, forever. In our org this flapped 15 repos on an hourly schedule until we noticed.
So there is no actor_id value for an OrganizationAdmin bypass actor that both deploys and converges.
Reproduction with compareDeep directly:
const md = new MergeDeep(log, github, [])
const orgAdmin = { actor_id: null, actor_type: 'OrganizationAdmin', bypass_mode: 'pull_request' }
md.compareDeep({ bypass_actors: [] }, { bypass_actors: [orgAdmin] }, {}, {}, {})
// => hasChanges: false (expected: an addition)
For ruleset
bypass_actors, GitHub's API ignoresactor_idwhenactor_typeisOrganizationAdmin(orDeployKey) and always returnsactor_id: nullfor those entries.MergeDeepcannot handle that shape, in either direction:Configured as
actor_id: null(matching what the API returns): the entry never deploys.NAME_USERNAME_PROPERTYpicksactor_idas the object's identity field because the key exists,GET_NAME_USERNAME_PROPERTYreturns the null, andprocessArraysskips entries with a falsy id (if (id) visited[id] = a). AnOrganizationAdminactor present in config but missing from the live ruleset produces no addition, no modification, and a "no changes" plan. The sync silently does nothing.Configured as
actor_id: 1(whatdocs/sample-settings/settings.ymlshows): the entry deploys, but on the next sync the config entry (id1) cannot match the live entry (idnull), so the same change is detected and re-applied on every run, forever. In our org this flapped 15 repos on an hourly schedule until we noticed.So there is no
actor_idvalue for anOrganizationAdminbypass actor that both deploys and converges.Reproduction with
compareDeepdirectly: