# When Background Agents Use User OAuth Tokens vs GitHub App Tokens for Pull Request Creation

> Learn when background agents use user OAuth tokens versus GitHub App tokens for pull request creation. Understand authentication fallbacks and optimal token usage for your workflow.

- Repository: [Cole Murray/background-agents](https://github.com/ColeMurray/background-agents)
- Tags: best-practices
- Published: 2026-07-13

---

**Background agents default to user OAuth tokens when the triggering user has valid stored credentials, falling back to GitHub App tokens only when user authentication is unavailable or expired.**

The ColeMurray/background-agents repository implements a hierarchical authentication strategy for pull request creation that prioritizes user identity while maintaining operational continuity for automated workflows. Understanding when the system selects **user OAuth tokens** versus **GitHub App tokens** is essential for debugging permission issues and maintaining proper audit trails in CI/CD pipelines.

## Token Selection Logic in SessionPullRequestService

The authentication decision occurs in [`packages/control-plane/src/session/pull-request-service.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/session/pull-request-service.ts) within the `createPullRequest` method. The service uses a nullish coalescing operator to prioritize user credentials:

```typescript
// Use user OAuth if available, otherwise fall back to GitHub App token
// (e.g. sessions triggered from Linear or other integrations without user GitHub OAuth)
const prAuth = input.promptingAuth ?? appAuth;

```

This logic executes at lines 74-78, where `input.promptingAuth` represents the user's OAuth token passed through `CreatePullRequestInput`, and `appAuth` represents the GitHub App installation token generated for the session.

## User OAuth Token Flow

### When User Tokens Are Selected

The system utilizes a **user OAuth token** when the prompting user has a valid GitHub OAuth token stored in the platform. This occurs in standard UI flows where the user has authenticated through the OAuth flow and the **ParticipantService** has successfully retrieved their credentials.

In [`packages/control-plane/src/session/participant-service.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/session/participant-service.ts), the service resolves the participant's stored OAuth token, refreshing it if necessary, and returns it as a `SourceControlAuthContext`:

```typescript
const input: CreatePullRequestInput = {
  title: "Add new feature",
  body: "Implemented XYZ.",
  repoOwner: "acme",
  repoName: "demo",
  promptingUserId: user.id,
  promptingAuth: { authType: "oauth", token: user.oauthToken }, // User token
  sessionUrl: "https://app.example.com/sessions/123",
};

const result = await pullRequestService.createPullRequest(input);

```

Using the user token preserves the user's GitHub identity, respects their specific repository permissions, and maintains branch protection rules and audit logs under their personal credentials.

## GitHub App Token Fallback

### Integration and Expiration Scenarios

The **GitHub App token** serves as the fallback mechanism when no user OAuth context exists. This occurs when sessions originate from external integrations like Linear, Slack, or background jobs that lack user credentials.

The App token is generated through the source control provider's `generatePushAuth()` method:

```typescript
const pushAuth = await this.deps.sourceControlProvider.generatePushAuth();
const appAuth: SourceControlAuthContext = {
  authType: "app",
  token: pushAuth.token,
};

```

This code appears at lines 90-101 in [`pull-request-service.ts`](https://github.com/ColeMurray/background-agents/blob/main/pull-request-service.ts). The resulting token carries the GitHub App installation permissions (typically **repo** and **contents** scopes) and enables the system to push branches and create pull requests without user intervention.

When handling Linear webhooks or similar integrations without user OAuth data, the input explicitly sets `promptingAuth` to null:

```typescript
const input: CreatePullRequestInput = {
  title: "Fix typo",
  body: "Corrected spelling.",
  repoOwner: "acme",
  repoName: "demo",
  promptingUserId: "linear‑bot",
  promptingAuth: null, // No user token available
  sessionUrl: "https://app.example.com/sessions/456",
};

```

### Token Refresh Failures

If a user's OAuth token expires and cannot be refreshed (for example, when no OAuth client secret is configured), the system gracefully degrades to the App token. In [`packages/control-plane/src/session/participant-service.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/session/participant-service.ts) at lines 215-221, the token refresh logic logs a warning and returns null, triggering the fallback mechanism in the pull request service.

## Key Implementation Files

The authentication flow spans several critical files in the control plane:

- **[`packages/control-plane/src/session/pull-request-service.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/session/pull-request-service.ts)** – Contains the core `createPullRequest` method and the token selection logic using the nullish coalescing operator.

- **[`packages/control-plane/src/session/participant-service.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/session/participant-service.ts)** – Handles OAuth token resolution and refresh logic, returning `SourceControlAuthContext` objects or null when tokens are unavailable.

- **[`packages/control-plane/src/auth/github.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/auth/github.ts)** – Defines GitHub OAuth configuration structures and error handling patterns.

- **[`packages/control-plane/src/source-control/github-provider.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/source-control/github-provider.ts)** – Implements `generatePushAuth()` to create App installation tokens for repository operations.

- **[`packages/control-plane/test/integration/create-pr.test.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/test/integration/create-pr.test.ts)** – Contains integration tests verifying both authentication paths.

## Summary

- **User OAuth tokens** are prioritized when the triggering user has valid stored credentials, preserving user identity and permissions.
- **GitHub App tokens** function as a fallback for external integrations (Linear, Slack) and expired user tokens, ensuring continuous operation.
- The token selection occurs at [`packages/control-plane/src/session/pull-request-service.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/session/pull-request-service.ts) lines 74-78 using the `??` operator.
- **ParticipantService** manages token refresh and validation at [`packages/control-plane/src/session/participant-service.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/session/participant-service.ts) lines 215-221.
- The `generatePushAuth()` method in the source control provider generates App tokens when user authentication is absent.

## Frequently Asked Questions

### When does a background agent use a user OAuth token instead of a GitHub App token?

A background agent uses a user OAuth token when the `promptingAuth` field in `CreatePullRequestInput` contains a valid `SourceControlAuthContext` with the user's token. This occurs when the session is triggered by a logged-in user who has previously authenticated through GitHub's OAuth flow and whose token is stored in the system.

### What happens if a user's OAuth token expires during pull request creation?

If the OAuth token expires and cannot be refreshed (due to missing OAuth client secrets or refresh token invalidation), the ParticipantService logs a warning and returns null. The pull request service then falls back to the GitHub App token, allowing the PR creation to proceed under the App's installation permissions rather than failing the operation.

### How does the system handle pull requests from external integrations like Linear?

For external integrations that lack user OAuth contexts, the system explicitly sets `promptingAuth` to null in the `CreatePullRequestInput` object. The `createPullRequest` method detects this null value and uses the GitHub App token generated by `generatePushAuth()` to push the branch and create the pull request, maintaining functionality without requiring user credentials.

### Where is the token selection logic implemented in the codebase?

The token selection logic is implemented in [`packages/control-plane/src/session/pull-request-service.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/session/pull-request-service.ts) at lines 74-78, where the code evaluates `const prAuth = input.promptingAuth ?? appAuth;`. This single line determines whether the subsequent GitHub API calls use the user's personal OAuth token or the installation's App token.