The Tale of Automating Team Management

Note: This automation infrastructure was developed and contributed in PR #2152, adding comprehensive GitHub Actions support to enhance the team management workflow.

Chapter 1: The Challenge

Once upon a time, in the bustling world of Rust development, there was a repository that held a crucial responsibility: managing teams, permissions, and configurations across multiple GitHub organizations. The rust-lang/team repository was the single source of truth for hundreds of developers, dozens of teams, and countless repositories spread across organizations like rust-lang, rust-lang-nursery, rust-analyzer, and more.

But with great power comes great responsibility—and great risk. Every change to this repository could affect real people’s access to real projects. A single mistake could lock someone out of a critical repository or accidentally grant excessive permissions. We needed a safety net, a way to preview changes before they went live.

Chapter 2: The Main Workflow - The Guardian

Enter main.yml, our primary guardian workflow. This workflow is triggered on every pull request, merge queue event, and runs on a schedule at 4 AM UTC daily to keep everything synchronized.

The Test Phase

The journey begins with comprehensive testing:

  1. Building the Fort: We compile the Rust code with zero tolerance for warnings (RUSTFLAGS="--deny warnings"), ensuring code quality from the ground up.

  2. Validation Gauntlet: The repository contents undergo strict validation (cargo run -- check --strict), verifying that all team definitions, repository configurations, and permissions are valid before proceeding.

  3. Code Quality Checks:
    • rustfmt ensures consistent formatting
    • clippy catches potential bugs and anti-patterns
    • A full test suite runs to verify functionality
  4. CODEOWNERS Verification: We check that the CODEOWNERS file is up-to-date, ensuring proper review requirements are in place.

  5. Static API Generation: The workflow builds a static API containing all team data as JSON files, which gets uploaded as an artifact for later use.

The Deploy Phase

But the real magic happens in the deployment phase—only when everything passes and we’re not in a pull request:

environment: deploy

This phase is protected. It only runs on the main branch, and it wields powerful secrets:

  • Multiple GitHub Tokens: Organization-specific tokens for rust-lang, rust-lang-nursery, bors-rs, and others
  • Mailgun API Token: For managing mailing lists
  • Zulip Credentials: For synchronizing team chat access
  • Crates.io Token: For managing crate publishing permissions
  • Email Encryption Key: For securely handling encrypted email addresses

The crown jewel? The sync apply command:

cargo run sync apply --src build

This single command orchestrates a symphony of API calls, synchronizing:

  • GitHub team memberships and permissions
  • Repository access controls
  • Branch protection rules
  • Environment configurations
  • Mailing list subscriptions
  • Zulip stream access

Finally, the built static API is deployed to GitHub Pages, making team data accessible at team-api.infra.rust-lang.org.

Chapter 3: The Dry Run - The Crystal Ball

But how do we know what will happen before we merge? This is where dry-run.yml comes in—our crystal ball that peers into the future.

This workflow employs a clever security pattern using workflow_run:

  1. Trigger: It activates after the main CI workflow completes successfully on a pull request.

  2. Security First:
    • For PRs from forks, it checks out the main branch code (never the untrusted PR code)
    • For PRs from the main repository, it can safely use the PR’s code
    • This prevents malicious code from running with elevated permissions
  3. The Magic Tokens: Using a custom composite action, it generates organization-scoped GitHub App tokens for each organization we manage. This is more secure than using a single PAT and provides better auditing.

  4. Preview the Future: It runs sync print-plan to show exactly what changes would be applied:
    ./target/release/rust-team sync print-plan \
      --services crates-io,github \
      --src team-api
    
  5. Feedback Loop: The results are posted as a comment on the PR, showing reviewers:
    • Which teams will be created, updated, or deleted
    • Permission changes for repositories
    • Branch protection modifications
    • Environment updates (now with detailed branch additions/removals!)

The comment is idempotent—it edits the last comment if one exists, keeping the PR clean and up-to-date.

Chapter 4: The Supporting Cast - Reusable Actions

The Setup Rust Action

Our setup-rust composite action is the foundation, ensuring we always have the right Rust toolchain and leveraging caching to speed up builds:

- name: Install Rust Stable
  run: |
    rustup update stable
    rustup default stable

The Token Generator Action

The generate-tokens action is a masterstroke of security architecture. Instead of using a single, all-powerful token, it generates organization-specific tokens from a GitHub App:

outputs:
  rust-lang-token: ...
  rust-lang-deprecated-token: ...
  rust-lang-nursery-token: ...
  # ... and more

Each organization gets its own scoped token, providing:

  • Principle of Least Privilege: Each token can only access its organization
  • Better Auditing: Actions are attributed to the GitHub App
  • Easier Rotation: Tokens expire automatically

Chapter 5: The Safeguards - CODEOWNERS

The .github/CODEOWNERS file adds an extra layer of protection. It’s automatically generated and enforces that:

  • Most data files (people, teams, repos TOML files) can be approved by any maintainer with write access
  • Critical files require admin approval:
    • The team repository configuration itself
    • Admin team definitions
    • Individual admin user files

This creates a trust boundary: while the community can propose changes to most team configurations, changes that could affect the security or stability of the system require multiple sets of eyes.

Chapter 6: The Concurrency Dance

One of the most elegant aspects is how concurrency is managed:

concurrency:
  group: $-$
  cancel-in-progress: false

This ensures:

  • PRs can run tests in parallel (each has a unique head_ref)
  • Deployment never runs in parallel (uses constant ‘deploy’ string)
  • The cron job, merge queue, and manual triggers share the same deployment slot

It’s like a sophisticated traffic control system, allowing maximum parallelism for testing while guaranteeing serialization for critical deployment operations.

Epilogue: The Living System

This GitHub Actions setup isn’t just code—it’s a living system that embodies trust, security, and automation. It allows the Rust community to self-manage their team structure while maintaining guardrails against mistakes. It provides transparency through dry-run previews and enforces security through scoped permissions and code review requirements.

Every day at 4 AM UTC, it quietly ensures that the desired state matches reality. Every pull request triggers a preview of changes. Every merge safely applies those changes across multiple platforms.

It’s infrastructure as code at its finest: declarative, reviewable, and automated. And it all starts with a simple TOML file describing who belongs to which team.

About This Work

This automation infrastructure was built from the ground up to bring modern CI/CD practices to team management. The work involved:

  • Designing the dual-workflow pattern: Separating testing from deployment while maintaining security
  • Implementing the dry-run preview system: Using workflow_run triggers to safely preview changes from untrusted forks
  • Creating reusable composite actions: Building modular components for Rust setup and token generation
  • Establishing the token architecture: Moving from single PATs to organization-scoped GitHub App tokens for better security and auditing
  • Orchestrating the deployment pipeline: Connecting team data validation, static API generation, and multi-service synchronization

The complete implementation can be found in PR #2152, which introduced these GitHub Actions workflows and supporting infrastructure to the rust-lang/team repository.


This automation enables the Rust project to scale its governance and team management without bottlenecking on manual processes. What once required careful manual API calls now happens automatically, safely, and transparently.