group-parity, and split the two resources add-to-group was straddling - #72
Merged
Merged
Conversation
Both of these came out of reviewing the m4 refresh path before the next wave,
and both are about the same thing: a wave that looks fine right up until a host
is already in production.
group-parity answers a question nothing in the toolchain could: do the hosts in
a group get the profiles a working production host gets? The m4-214 incident
turned on this — a host missing Skip Setup Assistant and the FDA SSH Keygen
Wrapper hung at first boot and presented as "Safari automation is broken",
costing most of a day. The postmortem's advice was to diff `profiles show` by
hand against a known-good host, which needs SSH to a box that by definition may
not be reachable. This asks SimpleMDM instead, before a wave starts.
Two findings shaped the implementation, both verified against the live account:
- Assignment-group records carry no `profiles` relationship at all; the link
lives on the profile side as `relationships.groups`. So the comparison is
per-device, via GET /devices/{id}/profiles.
- It has to be per-device for correctness, not just convenience. Diffing the
assignment groups reports Skip Setup Assistant and the FDA wrapper as
missing from the bootstrap group — true, and irrelevant: its devices get
both from the additive DEP Enrollment group. Crying wolf on exactly the pair
from the postmortem would train operators to ignore the check. A test pins
this against a well-meaning simplification.
The baseline is the intersection of several sampled reference devices rather
than one sampled host, so an atypical prod box can't drag a profile in and the
result doesn't depend on which device the API listed first.
A profile diff alone under-reports, though, so there is a second peer-wise pass:
a group that two thirds of the devices are in but some are not means those were
moved rather than added. That found three hosts missing Relops Public SSH Key,
Sudoers, Enable SSH and DEP Enrollment — no admin key, no passwordless sudo, no
sshd after their next wipe. Two were also in the prod group, so they still
received the profiles and the profile diff said nothing about them. Devices are
labelled with serial and name because a bare device id is not identifying: a
matching enrollment date was enough to mistake one for m4-214.
The second change: `add-to-group --quarantine-on-register` coupled an action
bound by the SimpleMDM API (three calls per host, no SSH to pace it) to a
~30-minute wait bound by Taskcluster, with one --concurrency serving both.
Raise it for wall-clock and you hammer SimpleMDM; lower it for SimpleMDM and 33
hosts serialise into five and a half hours. At -j12 this looked like five hosts
failing when twelve had already been added — the POST succeeded and the
follow-up push_apps 429'd — so killing the batch orphaned twelve live,
autonomous bootstraps with nothing watching them. They would have reached
production unvalidated. The runbook has carried a two-command workaround ever
since; this is that workaround, built in.
The add phase is now clamped to simplemdm_max_concurrent whatever -j says, and
warns rather than silently obeying. The watch phase then runs every host at
once, because a watcher is an idle poll loop. A host whose add FAILED is still
watched, since that is precisely the case where it is in the group anyway. The
bootstrap-spanning budget is passed as --max-wait-seconds, so operators no
longer need to know to export a 5400s override — a budget that expires early
puts the host live unheld, which is the failure the flag exists to prevent.
Both phases log under one batch directory, and Ctrl-C writes added.txt naming
every host bootstrapping unwatched, with the command to re-attach.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Two changes from reviewing the m4 refresh path before the next wave. Both are about the same failure shape: a wave that looks fine right up until a host is already in production.
1.
reprovision group-parityAnswers a question nothing in the toolchain could: do the hosts in a group get the profiles a working production host gets? Read-only, API-only, no SSH unless you pass
--host.The m4-214 incident turned on this — a host missing Skip Setup Assistant and the FDA SSH Keygen Wrapper hung at first boot and presented as "Safari automation is broken", costing most of a day. The postmortem's advice was to diff
profiles show -type configurationby hand against a known-good host, which needs SSH to a box that by definition may not be reachable.Two things verified against the live account shaped the implementation:
profilesrelationship at all (the keys are apps / device_groups / devices / media). The link lives on the profile side asrelationships.groups.The baseline is the intersection of several sampled reference devices, not one sampled host, so an atypical prod box can't drag a profile in and the result doesn't depend on API listing order.
A profile diff alone under-reports, so there's a second peer-wise membership pass: a group that two thirds of the devices are in but some are not means those were moved rather than added.
What it found on first run
TCC - CI Worker Support Binaries (PPPC)missing on 38/40 bootstrap-group devices — attached to prod2017918but not to2417981, on a SIP-ON wave where a profile is the only way system-level TCC grants land. Since fixed.Relops Public SSH Key,Sudoers,Enable SSHandDEP Enrollment— no admin key, no passwordless sudo, no sshd after their next wipe. Two of them (m4-211, m4-215) are also in the prod group, so they still receive the profiles and the profile diff said nothing about them. One of those two is staged for a wipe.Devices are labelled with serial and name, because a bare device id is not identifying — a matching enrollment date was enough for me to mistake one for m4-214.
2. Split the two resources
add-to-group --quarantine-on-registerwas straddlingIt coupled an action bound by the SimpleMDM API (three calls per host, no SSH to pace it) to a ~30-minute Taskcluster-bound wait, with one
--concurrencyserving both. Raise it for wall-clock and you hammer SimpleMDM; lower it for SimpleMDM and 33 hosts serialise into 5½ hours.At
-j12on 2026-08-14 this looked like five hosts failing when twelve had already been added — the POST succeeded and the follow-uppush_apps429'd — so killing the batch orphaned twelve live, autonomous bootstraps with nothing watching them. They would have reached production unvalidated. The runbook has carried a two-command workaround ever since; this is that workaround, built in.simplemdm_max_concurrentwhatever-jsays, and warns rather than silently obeying.skipped(exit 2) means it was definitely never added.--max-wait-seconds, so nobody needs to know to export a 5400s override. A budget that expires early puts the host live unheld — the failure the flag exists to prevent.Ctrl-Cwritesadded.txtnaming every host bootstrapping unwatched, with the command to re-attach.quarantine-on-registeris a first-class batch action now, replacing thexargs -P 33 nohupworkaround.Testing
ruffclean.group-parityexercised live against the real account (read-only), both group-wide and--host macmini-m4-111.--dry-runat-j12, confirming the clamp, the two phases and the explicit budget.Runbook and orchestrator README updated; the old manual
-jworkaround is preserved in a<details>block rather than deleted.🤖 Generated with Claude Code