This guide covers configuring and using automatic drift remediation with GitHub pull requests.
The drift remediation system can automatically create pull requests to fix detected drift. This is controlled by the GITHUB_REMEDIATION_ENABLED environment variable and integrates with the GitHubPRCreator adapter.
To create remediation PRs, you need a GitHub personal access token (PAT) with the following scopes:
| Scope | Purpose | Required |
|---|---|---|
repo (full repository access) |
Read/write access to code | Yes |
pull_requests:write |
Create and manage pull requests | Yes |
contents:write |
Create branches and commits | Yes |
Note: The repo scope includes both pull_requests:write and contents:write. If you want to be more restrictive, you can use fine-grained tokens with only the specific scopes needed.
repo (for full repository access)pull_requests:write, contents:write| Variable | Description | Required | Default |
|---|---|---|---|
GITHUB_REMEDIATION_ENABLED |
Enable/disable PR creation | No | false |
ENVIRONMENT |
Environment name | No | development |
GITHUB_TOKEN |
GitHub personal access token | Yes* | - |
DRIFT_REMEDIATION_TOKEN |
Alternative token source | No | - |
GITHUB_REPOSITORY |
Repository name (e.g., owner/repo) |
Yes* | - |
*Required when GITHUB_REMEDIATION_ENABLED=true
The remediation engine behaves differently based on the ENVIRONMENT variable:
| Environment | PR Creator | Behavior |
|---|---|---|
production |
GitHubPRCreator |
Creates real GitHub PRs |
development |
MockPRCreator |
Simulates PR creation (no real PRs) |
staging |
MockPRCreator |
Simulates PR creation (no real PRs) |
| Other | None |
No PR creation |
1
2
3
4
5
6
7
8
9
10
11
12
13
# Enable remediation with real GitHub PRs (production)
export GITHUB_REMEDIATION_ENABLED=true
export ENVIRONMENT=production
export GITHUB_TOKEN=ghp_your_token_here
export GITHUB_REPOSITORY=your-org/your-repo
# Enable remediation with mock PRs (development/staging)
export GITHUB_REMEDIATION_ENABLED=true
export ENVIRONMENT=development
# No token needed for mock mode
# Disable remediation entirely
export GITHUB_REMEDIATION_ENABLED=false
NEVER commit GitHub tokens to your repository. Use one of these secure storage methods:
1
2
3
4
5
# AWS Secrets Manager
aws secretsmanager get-secret-value --secret-id drift-remediation-token
# HashiCorp Vault
vault kv get -field=token secret/drift-remediation
1
2
# Load from .env file (never commit .env)
export GITHUB_TOKEN=$(cat .env.local | grep GITHUB_TOKEN | cut -d= -f2)
Regularly rotate your GitHub tokens:
1
2
3
4
5
6
from src.adapters.pr_creator import GitHubPRCreator
def token_provider():
return fetch_token_from_vault()
creator = GitHubPRCreator(token_provider=token_provider)
All remediation PRs are created as draft PRs by default. This requires:
Remediation PRs are automatically labeled with:
drift-remediation: Identifies the PR as auto-generated for driftauto-generated: Indicates it was created by the systemPR branches follow this pattern:
1
drift-remediation/{context}-{random-8-chars}
Example: drift-remediation/workbench-bff-a1b2c3d4
Title Format:
1
fix(drift): {action} {context}
Body Template:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
## Drift Remediation
**Action**: {action}
**Context**: {context}
**Node**: {node_id}
### Description
{suggestion_description}
### Labels
- drift-remediation
- auto-generated
---
*This PR was automatically generated by the SEA-Forge drift remediation system.*
The GitHubPRCreator includes automatic retry logic with exponential backoff:
| Attempt | Backoff |
|---|---|
| 1 | 1 second |
| 2 | 2 seconds |
| 3 | 4 seconds |
If all retries fail, the remediation is marked as failed but logged in the audit trail.
On 401/bad credentials errors:
token_provider is configured, the token is refreshed| Error | Cause | Solution |
|---|---|---|
No repository configured |
GITHUB_REPOSITORY not set |
Set the repository environment variable |
No GitHub token available |
Token not configured | Set GITHUB_TOKEN or DRIFT_REMEDIATION_TOKEN |
Not Found |
Invalid repository name | Verify repository format: owner/repo |
Resource not accessible |
Insufficient token permissions | Add repo scope or fine-grained permissions |
The remediation system provides idempotency through:
audit_idjust pipeline <context> multiple times produces the same result1
2
3
4
5
# View recent remediation actions
curl http://localhost:8000/drift/history?limit=10
# Or read the audit log directly
cat artifacts/workbench/drift-remediation-audit.jsonl | jq .
Each remediation action is logged as a JSON line:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"audit_id": "550e8400-e29b-41d4-a716-446655440000",
"node_id": "workbench-bff:api/user_routes",
"suggestion_id": "regenerate-from-spec",
"action": "regenerate",
"status": "success",
"pr_url": "https://github.com/org/repo/pull/123",
"error_message": null,
"timestamp": "2024-01-15T10:30:00Z",
"metadata": {
"branch": "drift-remediation-workbench-bff-a1b2c3d4",
"dry_run": false
}
}
| Status | Description |
|---|---|
success |
Remediation completed successfully |
failed |
Remediation failed (check error_message) |
skipped |
Remediation skipped (e.g., max PR limit reached) |
pending_review |
Requires manual review (not auto-fixable) |
Use MockPRCreator for local testing:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from src.adapters.pr_creator import MockPRCreator
from src.adapters.remediation_engine import RemediationEngine
mock_creator = MockPRCreator()
engine = RemediationEngine(pr_creator=mock_creator)
result = engine.execute(
node_id="test-node",
suggestion_id="regenerate-001",
create_pr=True,
dry_run=False,
)
# Verify mock behavior
assert len(mock_creator.created_prs) == 1
print(mock_creator.created_prs[0]["pr_url"])
# Output: https://github.com/mock/repo/pull/1
Test remediation without creating PRs:
1
2
3
4
5
6
7
8
9
result = engine.execute(
node_id="test-node",
suggestion_id="regenerate-001",
create_pr=False, # Skip PR creation
dry_run=True, # Simulate only
)
assert result.metadata["dry_run"] is True
assert result.status.value == "success"
1
2
3
4
5
6
7
8
# Run remediation PR tests
pytest services/workbench-bff/tests/test_remediation_pr.py -v
# Run all drift-related tests
pytest services/workbench-bff/tests/test_drift*.py -v
# Run with coverage
pytest services/workbench-bff/tests/test_remediation_pr.py --cov=src.adapters.pr_creator --cov=src.adapters.remediation_engine
1
2
3
echo $GITHUB_REMEDIATION_ENABLED # Should be "true"
echo $ENVIRONMENT # Should be "production" for real PRs
echo $GITHUB_REPOSITORY # Should be "owner/repo"
1
tail -f artifacts/workbench/drift-remediation-audit.jsonl | jq 'select(.status=="failed")'
1
2
curl -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/user/repos
If you encounter rate limiting:
GitHubPRCreator.INITIAL_BACKOFF_SECONDSIf the branch name collides:
Implement dynamic token refresh:
1
2
3
4
5
6
7
8
9
10
from src.adapters.pr_creator import GitHubPRCreator
def fetch_token():
# Custom token fetching logic
return secrets_manager.get("drift-remediation-token")
creator = GitHubPRCreator(
repo="org/repo",
token_provider=fetch_token
)
Specify a different target branch:
1
2
3
4
5
6
7
8
pr_result = creator.create_remediation_pr(
title="Custom PR",
body="Description",
branch="feature-branch",
changes=[...],
base_branch="main", # Default is "dev"
draft=True,
)
Limit the number of PRs created in a single batch:
1
2
3
4
5
6
7
from src.adapters.remediation_engine import RemediationConfig
config = RemediationConfig(
max_pr_per_run=3, # Maximum 3 PRs per batch
)
engine = RemediationEngine(config=config, pr_creator=creator)
docs/workdocs/auto-drift-healing-plan.md (P3.2 Automatic Drift Remediation)services/workbench-bff/src/adapters/pr_creator.pyservices/workbench-bff/src/api/drift_routes.pyservices/workbench-bff/tests/test_remediation_pr.pyFor issues or questions: