From b9119b25c33ec68b89c81707fa708ac0a2cdfbcc Mon Sep 17 00:00:00 2001 From: Jonathon Byrdziak Date: Wed, 25 Mar 2026 08:06:56 -0700 Subject: [PATCH] Add HTTP status and error logging to GitHub App token requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, getInstallationId() and generateInstallationToken() silently swallowed API errors — returning null with no indication of what GitHub actually returned. This made it impossible to diagnose token refresh failures in the watcher logs. Now both methods capture the HTTP status code and response body, logging the specific error (e.g. HTTP 401, 403, 500) before returning null. Co-Authored-By: Claude Opus 4.6 --- src/Helpers/GitHubApp.php | 43 ++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/Helpers/GitHubApp.php b/src/Helpers/GitHubApp.php index 75be276..5826f95 100644 --- a/src/Helpers/GitHubApp.php +++ b/src/Helpers/GitHubApp.php @@ -112,17 +112,31 @@ public static function generateJwt(string $appId, string $pemContents): ?string public static function getInstallationId(string $jwt, string $owner): ?int { $result = Shell::run( - "curl -s -H " . escapeshellarg("Authorization: Bearer {$jwt}") + "curl -s -w '\\n%{http_code}' -H " . escapeshellarg("Authorization: Bearer {$jwt}") . " -H 'Accept: application/vnd.github+json'" . " https://api.github.com/app/installations 2>/dev/null" ); if (!$result) { + Log::error('github-app', "installations list request returned empty response"); return null; } - $installations = json_decode($result, true); + // Separate body from HTTP status code + $lines = explode("\n", trim($result)); + $httpCode = (int) array_pop($lines); + $body = implode("\n", $lines); + + if ($httpCode < 200 || $httpCode >= 300) { + $parsed = json_decode($body, true); + $message = $parsed['message'] ?? trim($body); + Log::error('github-app', "installations list failed: HTTP {$httpCode} — {$message}"); + return null; + } + + $installations = json_decode($body, true); if (!is_array($installations)) { + Log::error('github-app', "installations response is not an array: " . trim($body)); return null; } @@ -133,6 +147,7 @@ public static function getInstallationId(string $jwt, string $owner): ?int } } + Log::warn('github-app', "no installation found for owner '{$owner}' among " . count($installations) . " installation(s)"); return null; } @@ -143,18 +158,36 @@ public static function getInstallationId(string $jwt, string $owner): ?int public static function generateInstallationToken(string $jwt, int $installationId): ?string { $result = Shell::run( - "curl -s -X POST" + "curl -s -w '\\n%{http_code}' -X POST" . " -H " . escapeshellarg("Authorization: Bearer {$jwt}") . " -H 'Accept: application/vnd.github+json'" . " https://api.github.com/app/installations/{$installationId}/access_tokens 2>/dev/null" ); if (!$result) { + Log::error('github-app', "installation token request returned empty response for installation {$installationId}"); + return null; + } + + // Separate body from HTTP status code + $lines = explode("\n", trim($result)); + $httpCode = (int) array_pop($lines); + $body = implode("\n", $lines); + + $data = json_decode($body, true); + + if ($httpCode < 200 || $httpCode >= 300) { + $message = $data['message'] ?? trim($body); + Log::error('github-app', "installation token request failed: HTTP {$httpCode} — {$message}"); + return null; + } + + if (!isset($data['token'])) { + Log::error('github-app', "installation token response missing 'token' field: " . trim($body)); return null; } - $data = json_decode($result, true); - return $data['token'] ?? null; + return $data['token']; } /**