CDNMac Engineering

Troubleshoot Universal Links association files from a cloud Mac

Troubleshoot Universal Links association files from a cloud Mac

A product link from a release branch works in the browser, but on a test device it always opens a web page instead of the app. Changing the app’s link-handling code at this point can miss the real failure: the device first needs to retrieve the domain’s association file and verify that the domain allows the specified app to handle the relevant paths. Use a cloud Mac to check the file-delivery chain, then retest on the device. Recording server and device evidence separately is more useful than repeatedly tapping the link.

Capture one failing case

Record the full link, the app expected to open it, what actually opens, the device’s OS version, and the app build version. Prepare at least two links: one the app should handle and one that should remain in the browser. “No links work” is not specific enough; different domains, paths, and query parameters may be governed by different rules.

Also pin down how the link is tested. Tapping a clickable link and typing its URL into the browser address bar are not equivalent tests. Use the same entry point for team retests. If production and preproduction use different domains, keep separate records; a file served by one domain cannot explain the behavior of another.

Put “what the server returns now” and “how this device opens the link now” in separate columns. The former can be checked immediately; the latter may depend on installation state and caching.

Check that the file is served directly

In the cloud Mac’s terminal, set the domain your team actually controls—not the app’s bundle identifier. The association file should be available directly at /.well-known/apple-app-site-association. Do not make the command follow redirects automatically: a successful response after a redirect could otherwise be mistaken for direct delivery.

export APP_DOMAIN='填入团队实际控制的域名'
test "$APP_DOMAIN" != '填入团队实际控制的域名' || exit 1
curl -sS -D aasa-headers.txt \
  -o aasa-body.json \
  "https://${APP_DOMAIN}/.well-known/apple-app-site-association"
cat aasa-headers.txt
wc -c aasa-body.json
plutil -lint -- aasa-body.json

Confirm that the HTTP status is 200, there is no Location pointing to a login page or another path, the body is not an HTML error page, and Content-Type is application/json. Passing plutil proves only that the syntax can be parsed, not that the rules are correct. If the command exits because of a network error, check DNS resolution and TLS connectivity first; do not treat an old aasa-body.json on disk as the result of this request. You can delete old files and rerun the check, then retain both the response headers and body.

Check more than one network entry point

If the file passes through a cache or reverse proxy, request the same URL from the network your team normally uses and from the cloud Mac. Compare the bodies and the cache-related response headers. If the results differ, check the deployment path, cache key, and invalidation process first. Do not hide the problem by broadly relaxing cache rules; the goal is for the association file on the same domain to return the expected content consistently.

Verify identifiers and path rules

Find the relevant entry under applinks.details in the JSON and compare its appID, character by character, with the team identifier and app bundle identifier used by the build. Then check each failing URL path against the rules. Whether paths are case-sensitive, whether a trailing slash is present, and whether an exclusion takes precedence over a broad rule must all be verified against the rule format actually used in the file. A working web route does not prove that the app can handle the link.

Item to check Evidence to record Common mistake
appID Value in the file and identifiers used by the build artifact Putting an identifier from another build environment in the file
URL path Full path and the rule expected to match it Testing only the domain’s home page, not the failing path
Exclusions Control link that should remain in the browser Letting a broad rule cover a path that should be excluded

Query parameters can help the app locate content after it opens, but they do not replace checking whether the URL path matches. If the team is migrating from legacy paths to components, first confirm that the target device OS supports the chosen format, then validate it with actual test links. Do not change identifiers, rules, and app code all at once without control cases.

Check the build and device next

Once the server file is correct, check the associated-domain configuration used to build the app. The domain entry must name the host that actually serves the file; subdomains are not interchangeable by default. Confirm that the build installed on the device is the one you just checked, not a package from another release branch. After changing the configuration, rebuild and reinstall the app. Refreshing the server file alone cannot change the configuration of an installed app.

Keep a record of the test device’s behavior before the change. Then install the new build and tap the same link from the same source. If the server returns the new rules but the device still behaves as before, reinstall the app and test again, recording both results. Do not assume retrieval and validation of the association file happen immediately; one tap is not enough to declare a server change ineffective.

Isolate the failing boundary instead of retrying everything

If the file cannot be retrieved or returns a redirect, fix delivery. If it is retrievable but the identifier or path does not match, fix the rules or build configuration. If both are correct but one device still fails, compare installation history, device version, and how the link was opened. Change one layer at a time, keeping the response headers, file body, build identifiers, and device observations from before and after each change. That is how you can tell which change worked.

Add the check to the release process

Add a lightweight check to the release job: request the association file from the production domain, require a response without redirects and with the correct status and parseable JSON, and check the rules against one path the app should handle and one it should not. Automated checks cover only the server and static configuration. You still need to verify which app opens the link on a device with the build installed.

Finally, put the failing case, actual response, matching rule, and device retest results in one record. The next time a link works on the web but not in the app, the on-call teammate can start at the most recent boundary known to have passed instead of guessing whether link-handling code or caching is at fault.

Frequently asked questions

Why does a link still open the website when the association file returns 200?

A 200 response only shows that the file is reachable. Check redirects, JSON, the app identifier, path matching, and the device’s associated-domain configuration.

How can I tell whether a recent file change is stuck in a cache?

Save the current server response headers and body first. Then reinstall the app on a test device and retest, recording server-side and device-side observations separately.

CDNMac M4

Run your next iOS build on a dedicated cloud Mac

Choose a fixed physical environment with M4, 16GB RAM, and a 256GB SSD, and pick the right fit for your task from six nodes and four rental terms.

Rent now