symfony/thanks · error · TransportException

errors

Error message

errors

What it means

The GitHub GraphQL API responded with one or more top-level errors and no partial 'data' payload, so call() throws a TransportException whose message is the first GraphQL error message returned by GitHub. This means the whole query failed server-side rather than individual fields being null; per-field errors with a 'path' would instead be recorded in $failures and stripped from the data. The library treats this as a transport-level abort because nothing usable was returned.

Solutions

  1. Check your GitHub token is set and valid (GITHUB_TOKEN or COMPOSER_GITHUB_TOKEN env vars) and re-authenticate if expired
  2. Retry later if rate-limited; GraphQL rate limits reset hourly — reduce query volume or raise the limit by authenticating
  3. Verify network/proxy access to api.github.com and retry on transient 5xx failures
  4. Update the composer plugin/library to the latest version in case the GraphQL query is outdated

Example fix

// before: run with no token
$ composer outdated --direct   # -> TransportException: Bad credentials
// after
$ export GITHUB_TOKEN=ghp_xxx   # or configure github.oauth in COMPOSER_HOME/auth.json
$ composer outdated --direct
Defensive patterns

Strategy: try-catch

Validate before calling

if (!getenv('GITHUB_TOKEN') && !getenv('COMPOSER_GITHUB_TOKEN')) {
    fwrite(STDERR, "No GitHub token configured; GraphQL calls may fail with 'Bad credentials'.\n");
}

Type guard

function hasUsableGraphqlPayload(?array $result): bool
{
    return $result !== null
        && isset($result['data'])
        && !isset($result['errors'][0]['message']) === false;
}

Try / catch

try {
    $repos = $client->getRepositories($urls);
} catch (Composer\Downloader\TransportException $e) {
    $msg = $e->getMessage();
    if (str_contains($msg, 'API rate limit exceeded')) {
        // wait for rate-limit reset, then retry
    } elseif (str_contains($msg, 'Bad credentials')) {
        // prompt user to refresh their GitHub token
    } else {
        throw $e;
    }
}

Prevention

When it happens

Trigger: Any call() to https://api.github.com/graphql (via getRepositories -> processChunks) where the JSON response contains result['errors'][0]['message'] but no result['data']: malformed GraphQL query, authentication failure (bad/expired GitHub token), rate limiting, or GitHub returning a top-level error object.

Common situations: Running composer-funding lookups with an unset or revoked GITHUB_TOKEN, hitting the GraphQL rate limit after many chunked queries, GitHub API 5xx responses serialized as an errors payload, or a library/version mismatch producing an invalid query shape.


AI-assisted analysis of symfony/thanks@f455cc9ba4 (2026-09-13). Data as JSON: /api/errors/b00109c0e33c5e4f. Report an issue: GitHub.

Appendix: source

Thrown at src/GitHubClient.php:180

        $options = [
            'http' => [
                'method' => 'POST',
                'content' => json_encode(['query' => $graphql]),
                'header' => ['Content-Type: application/json'],
            ],
        ];

        if ($this->rfs instanceof HttpDownloader) {
            $result = $this->rfs->get('https://api.github.com/graphql', $options)->getBody();
        } else {
            $result = $this->rfs->getContents('github.com', 'https://api.github.com/graphql', false, $options);
        }

        $result = json_decode($result, true);

        if (isset($result['errors'][0]['message'])) {
            if (!isset($result['data'])) {
                throw new TransportException($result['errors'][0]['message']);
            }

            foreach ($result['errors'] as $error) {
                if (!isset($error['path'])) {
                    $failures[isset($error['type']) ? $error['type'] : $error['message']] = $error['message'];
                    continue;
                }

                foreach ($error['path'] as $path) {
                    $failures += [$path => $error['message']];
                    unset($result['data'][$path]);
                }
            }
        }

        return isset($result['data']) ? $result['data'] : [];
    }

View on GitHub (pinned to f455cc9ba4)