Describe the bug
The Graph REST API has a limit of 100 entries per page. If one wants to fetch more records than that, there is a @odata.nextLink optional property in the API response to keep fetching in a loop. If I try to make a simple loop using @odata.nextLink, an infinite loop is created, because after the second page, the URL is malformed and it gets stuck on the second page.
Related command
az rest -u "https://graph.microsoft.com/v1.0/servicePrincipals"
Errors
Infinite loop due to pagination
Issue script & Debug output
I am running the following command:
az rest -u "https://graph.microsoft.com/v1.0/servicePrincipals"
e.g.:
az rest -u "https://graph.microsoft.com/v1.0/servicePrincipals"
# output is fine, we take @odata.nextLink and call it again
az rest -u "https://graph.microsoft.com/v1.0/servicePrincipals?$skiptoken=RFNwdAIAAQAAADVTZ...redacted"
# we get output, but now the @odata.nextLink is malformed:
az rest -u "https://graph.microsoft.com/v1.0/servicePrincipals?=RFNwdAIAAQAAADVTZXJ2aWNl...-->this_shouldnt_be_here<--&$skiptoken=RFNwdAIAAQAAADVTZXJ2aW...actual_token"
# this will now only return the second page forever, and the @odata.nextLink will keep getting larger and larger
Expected behavior
@odata.nextLink should not cause infinite loops
Environment Summary
azure-cli 2.77.0
core 2.77.0
telemetry 1.1.0
Extensions:
rdbms-connect 1.0.6
Dependencies:
msal 1.34.0b1
azure-mgmt-resource 23.3.0
Python location '/opt/az/bin/python3'
Config directory '/home/octavian/.azure'
Extensions directory '/home/octavian/.azure/cliextensions'
Python (Linux) 3.13.7 (main, Aug 26 2025, 08:47:13) [GCC 12.2.0]
Legal docs and information: aka.ms/AzureCliLegal
Your CLI is up-to-date.
Additional context
I was able to fix this by manually constructing the URL:
export async function fetchFromGraphApiWithPagination<T>(
url: string,
options?: Omit<shell.ExecOptions, 'async'> & Omit<ExtraOptions, 'exitOnFailure'>,
): Promise<T[]> {
const output: T[] = [];
let targetUrl = url;
while (true) {
const result = await executeCommand(`az rest -u "${targetUrl}"`, {
...options,
parseJson: true,
});
if (result?.value && Array.isArray(result.value)) {
output.push(...result.value);
}
// Manually handle skip token because Azure nextLinks are glitched and will infinite loop otherwise
if (result?.['@odata.nextLink']) {
const nextLink = new URL(result['@odata.nextLink']);
const skipToken = nextLink.searchParams.get('$skiptoken');
if (!skipToken) {
clack.log.error(
`ERROR: Azure Graph REST API pagination is glitching out.\nNext URL is missing skip token.\nRecord fetching cancelled to prevent infinite loop.\nYou will likely be missing some records.`,
);
break;
} else {
const newUrl = new URL(url);
newUrl.searchParams.set('$skiptoken', skipToken);
targetUrl = newUrl.toString();
}
} else {
break;
}
}
return output;
}
Describe the bug
The Graph REST API has a limit of 100 entries per page. If one wants to fetch more records than that, there is a
@odata.nextLinkoptional property in the API response to keep fetching in a loop. If I try to make a simple loop using@odata.nextLink, an infinite loop is created, because after the second page, the URL is malformed and it gets stuck on the second page.Related command
az rest -u "https://graph.microsoft.com/v1.0/servicePrincipals"Errors
Infinite loop due to pagination
Issue script & Debug output
I am running the following command:
az rest -u "https://graph.microsoft.com/v1.0/servicePrincipals"e.g.:
Expected behavior
@odata.nextLinkshould not cause infinite loopsEnvironment Summary
Additional context
I was able to fix this by manually constructing the URL: