Org structure and nested teams, the five permission levels and how they map to real roles, SSO enforcement, GitHub Apps vs OAuth Apps, fine-grained PATs, org-level rulesets, Enterprise Managed Users, audit log streaming, and billing governance — the decisions that define how your engineering org operates at scale
GitHub orgs have three categories of people and a flexible team hierarchy. Getting this structure right reduces access control friction and makes CODEOWNERS-enforced reviews tractable at scale.
| Category | Who | Billing seat? | Can be in teams? |
|---|---|---|---|
| Owner | Full admin access to the org; can add/remove members, configure SSO, manage billing | Yes | Yes |
| Member | Standard org member; access determined by team membership and repo permissions | Yes | Yes |
| Outside collaborator | External contributor added directly to specific repos only; not a full org member; no access to org settings or team discussions | Yes (for private repos) | No |
| GitHub Apps / bots | Machine accounts; installation-based permissions; not a seat | No | No |
Teams can be nested — child teams inherit their parent's repo permissions in addition to their own. This maps naturally to an engineering org chart:
@acme/contractors with limited permissions, add them as org members, and control access via the team. Removing from the team removes access to all repos at once.
GitHub's five permission levels are often misunderstood. Here's what each actually enables — and which real engineering role it maps to.
| Level | Key capabilities | Real role |
|---|---|---|
| Read | Clone, pull, view issues and PRs, comment on issues, open issues | Product managers, designers, stakeholders, documentation contributors, security auditors |
| Triage | Everything in Read + manage (label, close, assign, reopen) issues and PRs — but cannot push code or merge | QA engineers doing bug triage, developer advocates managing community issues, support teams |
| Write | Everything in Triage + push to branches, create and approve PRs, manage Actions runs, create releases | Software engineers — the default for all developers contributing to the codebase |
| Maintain | Everything in Write + manage repo settings (topics, description, merge options), push to protected branches (without bypassing rules), manage webhooks, manage GitHub Pages | Tech leads, senior engineers owning a service — can configure the repo without full admin |
| Admin | Everything + delete the repo, transfer the repo, force-push to any branch, bypass branch protection, manage access, manage secrets | Platform team, engineering managers — use sparingly; prefer Maintain for day-to-day leads |
SSO (Single Sign-On) ties GitHub identity to your corporate identity provider (Okta, Azure AD, Google Workspace). Enforcing SSO means a user removed from your IdP loses GitHub org access automatically — no manual deprovisioning required.
| SAML SSO | OIDC SSO (Enterprise) | |
|---|---|---|
| Provisioning | Manual invite + SCIM for auto-provisioning (requires a SCIM-capable IdP) | Automatic — accounts are provisioned/deprovisioned by the IdP (EMU only) |
| Token auth after SSO | PATs and SSH keys must be explicitly authorized for the SSO org via the UI | Tokens are scoped to the managed user's identity — no separate authorization step |
| Existing accounts | Users link their existing GitHub account to the SSO identity | EMU: GitHub creates new managed accounts; users cannot use personal GitHub accounts |
| Availability | GitHub Teams and Enterprise | GitHub Enterprise (EMU only) |
Org Settings → Authentication security → Enable SAML single sign-on → Require SAML SSO authentication. After enforcement:
After SSO is enforced, users must re-authorize their existing credentials:
SCIM (System for Cross-domain Identity Management) automates user and team provisioning from your IdP to GitHub. When an employee joins, their IdP group membership creates their GitHub team membership. When they leave, IdP deactivation removes GitHub access — no manual offboarding step.
@acme/payments-team on GitHub within minutes. This eliminates the manual "add to GitHub team" step in your onboarding runbook.
Both OAuth Apps and GitHub Apps let external services interact with GitHub on behalf of users or repos. They have fundamentally different permission models — understanding the difference is essential when building integrations or auditing what has access to your org.
| OAuth App | GitHub App | |
|---|---|---|
| Acts as | A specific user — calls the API as that user, using their permissions | An independent identity — calls the API as itself (installation token) or on behalf of a user (user access token) |
| Permission scope | Coarse OAuth scopes (repo, read:org, etc.) — all-or-nothing per scope | Fine-grained per-resource permissions (e.g. read pull requests on specific repos only) |
| Installation | Per-user authorization; the app has access wherever the authorizing user has access | Per-repo or per-org installation; owner explicitly chooses which repos the app can access |
| Tokens | Long-lived user OAuth tokens (no automatic expiry) | Short-lived installation tokens (expire in 1 hour); JWT for app-level auth |
| Rate limiting | Shared with the authorizing user (5,000 req/hr) | Higher limits per installation (up to 15,000 req/hr for larger orgs) |
| Webhook delivery | Repo-level webhooks only | Can subscribe to all events across all installed repos from a single endpoint |
| Best for | Simple personal tools; quick integrations where fine-grained control isn't critical | Production integrations, CI/CD tools, bots — anything that should be auditable and follow least-privilege |
Classic PATs were all-or-nothing: a repo scope token had full read/write access to every private repo the user could access, forever (no expiry). Fine-grained PATs (introduced 2022) fix both problems.
| Classic PAT | Fine-Grained PAT | |
|---|---|---|
| Repo scope | All repos the user can access, or all public repos | Specific repos only — you select which ones |
| Permission granularity | Coarse OAuth scopes (repo, gist, read:org…) | Per-resource permissions (e.g. read-only on pull requests of repo X) |
| Expiry | Optional (often set to "no expiry") | Mandatory — max 1 year; org policy can require shorter |
| Org approval | No approval required | Org owners can require approval before a fine-grained PAT can access org resources |
| SSO support | Must be separately authorized for SSO orgs | Org approval covers SSO authorization |
Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token. Key fields:
Org-level rulesets (covered partially in Phase 2 and 3) let an org owner enforce branch protection rules across all repos — including ones created in the future — without touching each repo individually. This is the most scalable way to ensure a security baseline.
Use repo topics as a lightweight feature flag for rolling out stricter rulesets:
An org-level ruleset can require a specific reusable workflow to pass on all PRs, across all repos. This is the mechanism for platform teams to enforce standards that individual teams can't opt out of:
Enterprise Managed Users (EMU) is GitHub's fully IdP-controlled identity model. Instead of developers using their personal GitHub accounts in the org, the IdP creates dedicated managed accounts for them. Everything about those accounts is controlled by the enterprise.
| Aspect | Regular GitHub org | EMU org |
|---|---|---|
| Account ownership | Developer owns their personal GitHub account; org is attached to it | Enterprise owns the managed account; it ceases to exist when the employee leaves |
| Username format | Developer's chosen username (alice) | Prefixed with enterprise slug (alice_acme) |
| Contributions | Contributions show on personal profile and persist after leaving | Contributions are tied to the managed account; they don't follow the developer |
| Personal projects | Developer can have personal repos alongside work repos on the same account | Managed accounts cannot create personal public repos or interact with non-enterprise public repos |
| PATs / SSH keys | Developer manages their own credentials | Admin controls allowed credential types and expiry; org policy enforced centrally |
| Access termination | Must manually remove from org when employee leaves | IdP deprovisioning immediately removes all access and suspends the managed account |
The org audit log records every significant action taken by org members — configuration changes, access grants, repo operations, secret management, and more. It's your forensic trail for security investigations and compliance audits.
| Category | Examples of logged events |
|---|---|
repo | repo created, deleted, transferred, visibility changed, forked |
team | team created, member added/removed, repo permission changed |
org | member invited/removed, SSO enabled/disabled, IP allow list changed |
secret_scanning | alert created, dismissed, bypass used, push protection enabled |
protected_branch | branch protection created/updated/deleted, rule bypassed |
workflows | workflow run created, approval given, secret accessed during run |
packages | package published, version deleted, package transferred |
oauth_application | app authorized, tokens revoked, app permission changes |
For continuous compliance, stream audit events to your SIEM (Splunk, Datadog, Azure Monitor, AWS S3/EventBridge) in near real-time:
repo.destroy event, (2) org.remove_member events not triggered by your normal offboarding workflow, (3) secret_scanning_push_protection.bypass, (4) protected_branch.policy_override, (5) admin role grants (org.update_member where role becomes admin). These five event types cover the most common unauthorized or accidental destructive actions.
GitHub costs have three components: seats (user licences), Actions compute minutes, and storage (Packages + LFS + artifact retention). At scale, unmanaged Actions usage can generate surprising bills.
| Component | Rate (approx) | Biggest cost drivers |
|---|---|---|
| Ubuntu runner minutes | $0.008/min | Long-running test suites, missing cache config, unnecessary workflows on every branch |
| Windows runner minutes | $0.016/min (2×) | Using Windows runners for tasks that could run on Linux |
| macOS runner minutes | $0.08/min (10×) | Running all tests on macOS when only UI tests need it |
| Storage | $0.008 GB/day | 90-day artifact retention, large LFS files, old package versions |
paths filters — don't run CI on docs-only changes.retention-days: 7 on transient build artifacts. Default 90-day retention accumulates storage cost fast.cancel-in-progress: true to all PR workflows.At org scale, repositories without clear lifecycle governance accumulate as zombie repos — no owner, stale CI, out-of-date dependencies, potential security exposure. Establish explicit policies for each lifecycle stage.
Transferring a repo moves it to a different owner (another org or user) while preserving issues, PRs, stars, and git history. GitHub sets up redirects from the old URL. Considerations:
Archived repos are read-only. Issues and PRs cannot be created; commits cannot be pushed; branch protection rules still apply but nothing can trigger them. The repo remains searchable and cloneable.
Archive when: a service is decommissioned, a library is superseded, an experiment ended. Do not archive as a substitute for documentation — add a README note explaining the repo's status and what replaced it before archiving.
Deletion is permanent (beyond GitHub's 90-day recovery window for Enterprise). Org policy should require:
gh issue list --json or the export API