From feeef728457bb234dd3ad50af7f249ad21d5f95d Mon Sep 17 00:00:00 2001 From: Vikas Singhal Date: Tue, 14 Jul 2026 15:45:38 +0530 Subject: [PATCH] fix(github): omit hook_attributes so the one-click App manifest is valid (v0.189.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The one-click "Create GitHub App" flow always failed with GitHub's "Invalid GitHub App configuration … 'url' wasn't supplied" (forcing manual creation). Root cause: the manifest included hook_attributes:{active:false} to turn off the webhook, but GitHub requires hook_attributes.url whenever the object is present — so it rejected the manifest, and the missing "url" was the WEBHOOK url, not our (present) top-level url. Fix: omit hook_attributes entirely = an App with no webhook, no required url. Confirmed against GitHub's manifest docs. Test 79/79. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 11 +++++++++++ package-lock.json | 4 ++-- package.json | 2 +- scripts/github-per-member-test.cjs | 5 ++++- src/server.ts | 4 +++- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64ee82f..59cc765 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,17 @@ new version heading in the same commit. ## [Unreleased] +## [0.189.0] — 2026-07-14 +### Fixed +- **One-click "Create GitHub App" now actually works — the manifest was invalid.** Every attempt failed on + GitHub with *"Invalid GitHub App configuration … 'url' wasn't supplied"*, forcing manual App creation. + Root cause: the manifest sent `hook_attributes: { active: false }` to disable the webhook, but + [GitHub requires `hook_attributes.url` whenever the object is present](https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest) + (even with `active:false`) — so GitHub rejected the whole manifest, and the "url" it meant was the + *webhook* url, not our (present) top-level one. Fix: **omit `hook_attributes` entirely** — that's an App + with no webhook, exactly what we want, and no required url. (`src/server.ts` `githubAppManifest`; + `scripts/github-per-member-test.cjs` now 79/79.) + ## [0.188.0] — 2026-07-14 ### Added - **Set the GitHub App slug by hand when it can't be auto-detected.** The "Install the App" button needs diff --git a/package-lock.json b/package-lock.json index 9d5336b..c77db41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "agent-os", - "version": "0.188.0", + "version": "0.189.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "agent-os", - "version": "0.188.0", + "version": "0.189.0", "license": "MIT", "bin": { "agent-os": "bin/agent-os" diff --git a/package.json b/package.json index 6fc04b1..7d26fa1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "agent-os", - "version": "0.188.0", + "version": "0.189.0", "description": "A generic, governed operating system for running autonomous agents safely across brands. Ships with a local web console.", "license": "MIT", "type": "commonjs", diff --git a/scripts/github-per-member-test.cjs b/scripts/github-per-member-test.cjs index d31b4fd..8d72211 100644 --- a/scripts/github-per-member-test.cjs +++ b/scripts/github-per-member-test.cjs @@ -234,7 +234,10 @@ async function main() { assert(Array.isArray(manifestObj.callback_urls) && manifestObj.callback_urls[0].endsWith('/api/github/callback'), 'manifest carries our OAuth callback URL'); assert(manifestObj.redirect_url.endsWith('/api/github/manifest-callback'), 'manifest carries the manifest-callback URL'); assert(manifestObj.default_permissions.contents === 'write' && manifestObj.default_permissions.pull_requests === 'write', 'manifest requests least-privilege Contents + PR write'); - assert(manifestObj.hook_attributes.active === false && manifestObj.public === false, 'manifest disables webhook + keeps the App private'); + // hook_attributes MUST be omitted: GitHub requires hook_attributes.url whenever the object is present, + // so including it (even active:false) makes GitHub reject the manifest with "url wasn't supplied". + assert(!('hook_attributes' in manifestObj) && typeof manifestObj.url === 'string' && manifestObj.url.length > 0, 'manifest omits hook_attributes (no webhook) + carries the required top-level url'); + assert(manifestObj.public === false, 'manifest keeps the App private'); const mState = new URL(man.postUrl).searchParams.get('state'); const rOrg = await call('GET', '/api/github/manifest?org=acme-inc'); diff --git a/src/server.ts b/src/server.ts index bd40bda..9bcbcab 100644 --- a/src/server.ts +++ b/src/server.ts @@ -4528,10 +4528,12 @@ function githubRedirectUri(req: http.IncomingMessage): string { */ function githubAppManifest(req: http.IncomingMessage, os: AgentOS): Record { const origin = publicOrigin(req); + // NB: do NOT include `hook_attributes` — GitHub requires `hook_attributes.url` whenever the object is + // present (even with `active:false`), so sending `{active:false}` makes GitHub reject the whole manifest + // with "url wasn't supplied". Omitting it entirely = no webhook, which is exactly what we want. return { name: `Agent OS — ${os.tenantName}`, url: origin, - hook_attributes: { active: false }, redirect_url: `${origin}/api/github/manifest-callback`, callback_urls: [`${origin}/api/github/callback`], setup_on_update: false,