Create and Install a GitHub App for an Organization

Site github.comTask create-github-app-and-installVersion v3Updated Aug 31, 2026Category account-management

Create a GitHub App for an organization, configure its callback, webhook, and repository permissions, install it on selected repositories, and optionally update an existing installation. This skill was captured from a live agent session on github.com and is published here as a reusable recipe for agents.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

Create a new GitHub App owned by {organization}, configure {app-name}, {description}, {homepage-url}, webhook activation, and requested repository permissions, then install it on all or selected organization repositories. The same skill can optionally update an existing installation. This is a mutating workflow. Never expose private keys, client secrets, installation tokens, cookies, TOTP values, or other authentication artifacts.

If a caller requests a private key or other secret, deliver it only through an approved SOPS-backed secret channel when one is available. Never inspect, print, return, download, or place secret material in browser-evaluation results. If secure delivery is unavailable, do not generate the secret.

Use Cases

  • Creating a GitHub App owned by an organization.
  • Assigning repository permissions during app creation.
  • Installing the app on all or selected organization repositories.
  • Updating repository selection for an existing installation.
  • Repeating the flow for any organization, app, and repository without guessing opaque IDs.
  • Handling GitHub Mobile or authenticator-app sudo challenges in the same browser session.

Automation Flow

Create and install a new app

  1. Normalize {organization}, {app-name}, {description}, {homepage-url}, {webhook-active}, {permissions}, and {repository-selection}. Preserve supplied URLs exactly. Do not invent organization IDs, app slugs, repository option IDs, or installation IDs.

  2. In the authenticated browser profile, navigate directly to: https://github.com/organizations/{organization}/settings/apps/new If authentication is required, use the separate GitHub sign-in workflow. Stop for CAPTCHA, SSO, unresolved two-factor authentication, or other authentication walls.

  3. Fill:

    • input#integration_name{app-name}
    • textarea#integrator_description{description}
    • input#integration_url{homepage-url}
    • input[type='checkbox'][name='integration[hook_attributes][active]']{webhook-active}
  4. Expand the application-permissions section if necessary and configure only requested permissions. Prefer semantic controls such as button#integration_permission_contents_{read|write}, button#integration_permission_issues_{read|write}, and button#integration_permission_pull_requests_{read|write}. If a permission is not covered, inspect section[aria-label='repository-permissions'] button and match the visible permission and access level. Do not persist generated action-menu-* IDs.

  5. Submit with the live creation form's commit control, conventionally input[name='commit']. Wait for navigation and read the resulting URL, normally: https://github.com/organizations/{organization}/settings/apps/{app-slug} Treat {app-slug} as opaque and obtain it from the resulting URL.

  6. Generate a private key only when explicitly requested. Use the live Generate a private key action, then transfer it directly to the approved SOPS-backed secret channel without evaluating or exposing its contents. Otherwise do not generate one.

  7. Start installation directly at: https://github.com/apps/{app-slug}/installations/new If GitHub redirects to /permissions?target_id={organization-id}&target_type=Organization, preserve the live opaque {organization-id} and continue; never fabricate it.

  8. Select the organization target using input#install_target_selected. Choose all repositories or selected repositories according to {repository-selection}. For selected repositories, open the repository selector, use input[aria-label='Search for a repository'] when needed, match the exact visible {owner}/{repo} name, read the live opaque option value from button[role='menuitem'], and select that option. Never derive the value from the repository name. Repeat for each requested repository.

  9. Submit with button[type='submit'], wait for navigation, and verify the resulting installation settings URL, commonly: https://github.com/organizations/{organization}/settings/installations/{installation-id} Preserve {installation-id} from the resulting URL.

Update an existing installation

  1. If {installation-id} is supplied, navigate directly to: https://github.com/organizations/{organization}/settings/installations/{installation-id} Otherwise navigate to https://github.com/organizations/{organization}/settings/installations, locate the intended app by visible name, and read its live numeric installation link before navigating.

  2. If sudo reauthentication appears, select only the live semantic option supported by the runtime. GitHub Mobile may use button[data-action='click:sudo-credential-options#showGitHubMobile'] or a visible control labeled Use GitHub Mobile; the authenticator route may use button[data-action='click:sudo-credential-options#showTotpApp']. These controls start a challenge; they are not credentials. Ask the caller or supported runtime authenticator to complete the challenge, and stop for unresolved CAPTCHA, recovery-code, or second-factor requests.

  3. Open the repository-selection menu using summary[role='button'] when present. Match the exact visible full repository name, read its live option value, and click the corresponding button[role='menuitem'][value='{repository-option-id}']. Submit with button[type='submit'].

Verification

  1. Run this evaluator once on the current page:
(() => {
 const clean = s => (s || '').replace(/\s+/g, ' ').trim();
 const text = clean(document.body?.innerText);
 const url = location.href;
 const selected = [...document.querySelectorAll('input[name="repository_ids[]"]')]
   .filter(input => input.checked || input.getAttribute('aria-checked') === 'true')
   .map(input => input.value);
 const installed = [...document.querySelectorAll('button[aria-label^="Remove "]')]
   .map(button => clean(button.getAttribute('aria-label')).replace(/^Remove /i, ''));
 const appMatch = url.match(/\/settings\/apps\/([^/?#]+)/i);
 const installationMatch = url.match(/\/settings\/installations\/(\d+)/i);
 const appId = text.match(/\bApp ID\s*[::]?\s*([0-9]+)/i)?.[1] || null;
 const clientId = text.match(/\bClient ID\s*[::]?\s*([A-Za-z0-9_-]+)/i)?.[1] || null;
 const privateKeyExposed = /BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY|Private key contents/i.test(text);
 const diagnostics = [...document.querySelectorAll('[role="alert"], .flash, .js-flash-alert')]
   .map(e => clean(e.textContent)).filter(Boolean).slice(0, 20);
 return {
   success: !/error|failed|unable to save/i.test(text) &&
     (selected.length > 0 || installed.length > 0 || /installation settings|repositories/i.test(text)),
   url,
   app_slug: appMatch ? decodeURIComponent(appMatch[1]) : null,
   app_id: appId,
   client_id: clientId,
   installation_id: installationMatch ? installationMatch[1] : null,
   selected_repository_ids: selected,
   installed_repository_names: installed,
   private_key_exposed: privateKeyExposed,
   sudo_or_2fa_present: /sudo|two-factor|authenticator app|GitHub Mobile|authentication code/i.test(text),
   diagnostics
 };
})()
  1. Return success only when each requested mutation is independently verified. Report private_key_generated and private_key_returned separately, with private_key_returned true only when the approved SOPS-backed channel confirms secure delivery. Never include private-key material, credentials, cookies, tokens, client secrets, or TOTP values.

Possible Friction Points

  • Organization app creation is at /organizations/{organization}/settings/apps/new; a homepage visit is unnecessary.
  • Created app URLs contain an opaque slug: /organizations/{organization}/settings/apps/{app-slug}. Always use the post-submit URL.
  • Installation URLs may contain an opaque numeric organization ID. Read it from GitHub's redirect or live link.
  • Repository installation and selection controls expose opaque numeric values. Match the visible full repository name, then use the live option/input value.
  • Existing installation settings may require sudo. GitHub Mobile is exposed through a semantic control such as button[data-action='click:sudo-credential-options#showGitHubMobile']; selecting it only starts the approval challenge.
  • The authenticator-app option may be exposed through button[data-action='click:sudo-credential-options#showTotpApp']; never place the TOTP itself in a selector or evaluator.
  • Dynamic action-menu-* IDs, repository option values, app slugs, organization IDs, and installation IDs are unstable or opaque. Read them live and never guess them.
  • Creating an app, installing it, generating a private key, and changing an existing installation are separate mutations; verify each requested state independently.
  • The observed permissions included repository contents and issues write access, but these are optional caller-requested settings, not mandatory defaults.
  • Organization settings require appropriate permissions. Stop on authorization walls, SSO approval, CAPTCHA, missing second factor, recovery-code prompts, or email verification.
  • Browser automation may reach a login or sudo wall even when navigation succeeds; inspect the live page before claiming the mutation completed.
  • SOPS is a secure delivery/storage mechanism outside GitHub's DOM. Do not assume it is available, and never log secrets while handing them to it.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=github.com&task=create-github-app-and-install