GitHub GH-600 Agentic AI Developer Certification — Complete Exam Prep Guide
Practice as you learn. Work through every GH-600 question with verified answers and the exact code snippets in the GitHub exam answers hub, or drill the whole exam with timed, personalized practice tests on ExamRoll.io.
GitHub Actions Workflow Design for Agentic SDLC
Agentic development introduces autonomous actors — Copilot coding agent, Copilot cloud agent tasks, and custom MCP-enabled agents — into the software delivery lifecycle. GitHub Actions is the control plane that constrains what those agents can do, when they run, and what evidence they leave behind. Every workflow choice — trigger, permission, artifact, condition — becomes a security and correctness boundary.
Triggers and Events for Agent Workflows
The trigger determines when an agent-facing workflow runs and what context it receives. The most heavily tested triggers are pull_request, workflow_dispatch, workflow_run, issues, and merge_group.
pull_request is the canonical validator for agent-produced work. When Copilot coding agent opens a draft PR from an assigned issue, a validation workflow keyed on pull_request with activity types opened, synchronize, and ready_for_review inspects the change without needing write scopes:
on:
pull_request:
types: [opened, synchronize, ready_for_review]
permissions:
contents: read
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
workflow_dispatch provides manual invocation. A frequent trap: candidates assume that a workflow_dispatch-only workflow will fire when someone labels an issue or when Copilot posts a plan. It will not. Labels require on: issues: types: [labeled]; dispatch requires an explicit human click or API call. Similarly, workflow_dispatch alone does not prove approval of an agent plan. It only proves someone triggered the workflow. Approval is enforced by environment required reviewers, not by the trigger.
workflow_run chains workflows. The escalation pattern — open an issue when a validation workflow fails — uses:
on:
workflow_run:
workflows: ["validate-agent-pr"]
types: [completed]
permissions:
issues: write
jobs:
escalate:
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
runs-on: ubuntu-latest
steps:
- run: gh issue create --title "Validation failed" --body "..."
env: { GH_TOKEN: ${{ github.token }} }
merge_group is the trigger for GitHub’s merge queue. If a required check does not list merge_group in its on: block, the queue will see it as skipped-but-pending and PRs will never merge. Forgetting merge_group is one of the most common traps.
Reusable workflows must live on the default branch of the source repository to be callable; a reusable workflow on a feature branch will not resolve. Forks are also gated: pull requests from forks do not run workflows automatically until a maintainer approves the first-time contributor.
Least-Privilege Workflow Permissions
The GITHUB_TOKEN scope is set through permissions:. The correct default is contents: read at the workflow level, with elevated scopes granted only on the specific job that needs them.
permissions:
contents: read
jobs:
deploy:
environment: production
permissions:
contents: read
packages: read
id-token: write
Granting contents: write to every job “for consistency” is always wrong: it lets any compromised step, action, or agent modify repository content. Commenting on PRs requires pull-requests: write; uploading SARIF from code scanning requires security-events: write; reading GHCR packages requires packages: read; the escalation-issue pattern above requires issues: write. Missing any of these produces a 403 at runtime.
Secrets have a parallel hierarchy. Agent secrets (used by Copilot coding agent and MCP servers) are distinct from Actions secrets. Configuring an MCP server credential in Actions secrets will not expose it to the agent. Repository secrets are not injected into workflows triggered from forks unless the maintainer explicitly re-runs with approval.
Artifact Management and Durability
actions/upload-artifact@v4 is the durability boundary between jobs and across runs. Two traps dominate this subdomain:
- Path mismatch — the
path:glob must match files that actually exist at the time of upload. If a test step writes toreports/but the upload specifiespath: coverage/, the artifact will be created empty or the step will fail with “no files found.” - Overwriting a shared latest artifact — under v4, artifact names must be unique within a run. If multiple matrix legs upload to
name: build-output, later uploads collide. Namespace with the run ID or matrix values:
- uses: actions/upload-artifact@v4
if: always()
with:
name: agent-logs-${{ github.run_id }}-${{ matrix.os }}
path: logs/**
$GITHUB_STEP_SUMMARY is a Markdown sink that renders in the run UI — the correct place to publish an agent’s structured plan or a validation summary for human review. if: always() ensures artifacts and summaries are captured even when prior steps failed.
Conditional Execution and Failure Semantics
Four status functions govern step execution: success() (default), failure(), cancelled(), and always(). if: always() runs regardless of upstream outcome; if: failure() runs only when a prior step in the same job failed; if: cancelled() catches manual cancellation.
Across jobs, use needs.<job>.result which resolves to success, failure, cancelled, or skipped:
escalate:
needs: [validate]
if: ${{ needs.validate.result == 'failure' }}
Bounded retry means capping retries (a small integer, or a timeout) rather than looping indefinitely — infinite retry on an agent step can burn minutes and mask genuine failure.
Matrix, Needs, and Concurrency Orchestration
matrix: fans a job out across parameter combinations; needs: fans results back in. fail-fast: false prevents one matrix leg’s failure from cancelling siblings — essential when you want the full evidence set from an agent’s parallel validations.
concurrency: prevents overlapping runs. A per-PR group is the standard pattern so that a new push cancels the stale run for the same PR:
concurrency:
group: agent-validate-${{ github.ref }}
cancel-in-progress: true
Path Filters and the Skipped-Required-Check Trap
paths: and paths-ignore: scope when a workflow runs. The trap: a required check that is filtered out by paths-ignore: ['docs/**'] will report as skipped rather than success. Branch protection treats skipped required checks as pending, blocking merge. The fix is either to drop the path filter on required checks, or to use a companion job that always reports success for filtered paths. The same applies to .github/workflows/** and db/migrations/**/*.sql filters.
Repository Scoping and Guards
Reusable and organization-wide workflows sometimes get copied into unrelated repositories. Guard the job body with a repo condition so the workflow no-ops elsewhere:
jobs:
validate:
if: github.repository == 'octo-org/customer-api'
Actor conditions (github.actor == 'dependabot[bot]' or checks for the Copilot agent actor) further scope who can invoke privileged paths. These guards prevent a copied validation workflow from running in the wrong repository with production secrets.
Critical rules: workflow_dispatch never proves approval — human gates come from environment required reviewers. Include merge_group in required-check triggers or the merge queue will stall on pending checks. paths-ignore on a required check produces “skipped,” which blocks merge. Artifact name: must be unique per run; namespace with ${{ github.run_id }}. Reusable workflows must be on the default branch. Agent secrets and Actions secrets are separate hierarchies. Forks do not run CI automatically.
Governance, Guardrails, and Autonomy Policies
Governance is the largest domain on the exam because agentic development inverts the traditional trust model: code is now proposed by a non-human actor that can move fast, touch many files, and open pull requests unattended. The platform’s job is not to trust the agent — it is to constrain the agent inside the same review, branch protection, and deployment gates that apply to humans, and often stricter ones. The agent is a contributor, never an approver.
Branch Protection, CODEOWNERS, and Required Checks
Branch protection is the load-bearing wall of agent governance. When Copilot coding agent is enabled on a repository, the agent can create its own implementation branch and open a draft pull request, but it cannot bypass rules configured on the target branch. A correctly protected main branch requires a pull request before merging, requires status checks to pass, requires review from Code Owners, and disallows force pushes. Under this configuration, an agent-authored PR must clear the same gates as any human PR — including CODEOWNERS review on any path the CODEOWNERS file governs.
A repository ruleset that restricts branch creation and updates to refs/heads/main does not block the agent from creating a separate implementation branch (e.g., copilot/fix-123); it only blocks direct writes to main. Draft status is a UI/workflow signal, not a bypass of protection.
Required status checks must include the merge_group event when a merge queue is used, otherwise checks run on the PR head but not on the queued merge commit. Similarly, referencing retired or renamed checks in required-check settings will block merges indefinitely because the check will never report; always audit required check names when workflows are refactored.
CODEOWNERS is the single most important control against agent overreach into sensitive paths. If /workflows/, /payments/, or migration directories are owned, the agent’s PR touching those files cannot merge without the listed owner’s approval. The correct mitigation when an agent modifies a workflow file is always Review the draft PR and require owner approval — never let passing checks substitute for ownership review, and never remove CODEOWNERS coverage from a path just because the agent frequently touches it.
Risk-Based Autonomy and Human-in-the-Loop Control
The exam frames governance design as a four-step method: (1) map repository paths to operational, security, and compliance impact; (2) define allowed actions for each autonomy level (autonomous merge, draft-only, human-approved execution); (3) attach human approval gates to high-risk transitions via CODEOWNERS and required environments; (4) validate with sample Copilot PRs and later tune. Blanket approval for every PR and blanket removal of review are both wrong extremes — the exam rewards risk-proportional gating.
The autonomy spectrum runs from fully autonomous (low-risk documentation changes that can merge after CI passes) through draft-only (agent produces the PR but a human merges) to human-approved execution (agent cannot proceed until a reviewer explicitly approves via an environment gate). The exam expects you to assign each path class to the correct tier, not to apply the same tier everywhere.
Plan-vs-Execution Gates and Approval Labels
The plan/execute separation is the most tested governance concept. A planning workflow reacts to an agent-plan label on an issue, performs impact analysis, and comments on the issue — it must not create branches, open PRs, or modify files. An execution workflow reacts to an agent-execute label and may open a draft PR. The two workflows must be separate and must not be combined into a single trigger.
The agent-approved label (or equivalent) is applied by a human reviewer after inspecting the plan. It is the signal that authorizes execution. Any workflow that applies this label automatically — without human review — defeats the gate. The exam tests this with scenarios where a bot or automation applies the approval label; the correct answer is always that this is wrong because it removes the human-in-the-loop requirement.
Compliance and Deployment Policy Governance
For regulated services (PCI, HIPAA, SOC 2), the exam expects stricter controls: deployment to production requires a named human approver recorded in the deployment audit log, secrets are isolated to the production environment (not at the repository level), and the deployment workflow uses workflow_dispatch (not an automatic trigger) so that every production change is a deliberate human act. Auto-triggered deployments from agent PRs are always wrong in regulated contexts.
Critical rules: The environment: key is the only recognized approval gate — neither workflow_dispatch nor pull_request triggers constitute human approval. Environment secrets are unavailable until a required reviewer approves. Attach environment: only to the job that performs the privileged action. A workflow cannot approve its own environment. Draft PRs from Copilot are proposals, not approvals.
Environments and Deployment Controls
GitHub Environments are the mechanism that separates validation from privileged deployment. An environment defines a named gate — with optional required reviewers, wait timers, and deployment protection rules — that a job must pass before its steps execute and before its secrets become available. This gate is the only recognized human-approval mechanism in GitHub Actions; no other workflow construct (labels, comments, dispatch triggers) substitutes for it.
Environment-Gated Approvals
A job that declares environment: production will pause before executing its steps and wait for a required reviewer to approve the deployment. Only after approval do the environment’s secrets become available to the job. This ordering is critical: secrets are not pre-loaded into the job environment and then gated — they are withheld until the gate passes. A workflow that references secrets.PROD_DB_PASSWORD in a job without an environment: declaration will receive the secret immediately, bypassing any intended gate.
A minimal agent-integration workflow looks like this:
on:
pull_request:
permissions:
contents: read
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
integration:
needs: validate
if: github.event.pull_request.draft == false
environment: agent-integration
runs-on: ubuntu-latest
steps:
- run: ./deploy-integration.sh
Here validate runs on every Copilot PR immediately, providing fast feedback. Only integration waits for the reviewer on agent-integration. A workflow cannot self-approve its own environment — approvals must come from a human listed as a required reviewer, and the approver cannot be the same identity that triggered the run (unless self-review is explicitly enabled).
Deployment Gating and Rollout/Rollback Patterns
The canonical rule for rollback: detection may be automatic; remediation must be human-initiated.
A compliant recovery workflow separates two concerns:
| Job | Trigger | Permissions | Human gate |
|---|---|---|---|
detect | workflow_run on failed release | issues: write | None — opens incident issue automatically |
rollback | workflow_dispatch with rollback_sha input | deployments: write, contents: write | environment: production-rollback with required reviewers |
The detect job captures the failing SHA into the incident issue body — losing this SHA breaks auditability. The rollback job must not be chained automatically from detection. If rollback runs on workflow_run: completed with conclusion == failure, you have created an auto-remediating agent, which is wrong for three reasons: it fuses detect and rollback into one path, removes the human-approved-rollback audit record, and can amplify a bad release if the detector misfires.
The correct pattern uses workflow_dispatch with an explicit rollback_sha input, so an operator consciously supplies the target commit, and environment: production-rollback forces a required reviewer to co-sign the action:
on:
workflow_dispatch:
inputs:
rollback_sha:
required: true
jobs:
rollback:
environment: production-rollback
permissions:
deployments: write
contents: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { ref: ${{ inputs.rollback_sha }} }
- run: ./deploy.sh
Separating Build/Test from Privileged Deploy
Least privilege in agentic pipelines means the build and validation jobs — which execute agent-generated code — must be unable to affect production even if the agent behaves adversarially. The correct ordering: (1) set workflow-level permissions: contents: read as the default; (2) configure the production environment with required reviewers before any job references it; (3) reference the production environment only in the deploy job; (4) grant deployment-specific permissions (deployments: write, contents: write, id-token: write) only in the deploy job.
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps: [ ... ] # read-only; no environment
deploy:
needs: build
environment: production
permissions:
contents: read
deployments: write
id-token: write
runs-on: ubuntu-latest
steps: [ ... ]
Production secrets live on the production environment, not at the repository level, so the build job cannot read them. “Leaking production secrets into validation” is prevented by this structure, not by trust in the agent.
Critical rules: The environment: key is the only recognized approval gate. Environment secrets are unavailable until a required reviewer approves. Detection is automatic; rollback is manual — use workflow_dispatch with an explicit rollback_sha input, gated by a production-rollback environment. Always capture the failing SHA in the incident record before rollback. A workflow cannot approve its own environment regardless of token scope. Draft PRs from Copilot are proposals, not approvals.
Multi-Agent Orchestration and Handoffs
Multi-agent orchestration on GitHub means composing multiple Copilot cloud agents, custom agents, and specialist workflow jobs so that plans, implementations, and reviews flow reliably from one stage to the next. The difference between a working multi-agent pipeline and a silently broken one is almost always a subtle contract violation: a missing needs dependency, an artifact name mismatch, a summary written before all specialists finished, or a resume that continues from stale context.
Fan-Out / Fan-In Patterns and Summary Gates
The fan-out/fan-in pattern dispatches independent specialist agents in parallel (fan-out), lets each produce evidence, and then converges their outputs into a single aggregate summary (fan-in). The correct ordering is: define contracts → generate plan → run specialists in parallel → aggregate.
The delayed fan-in requirement is critical. The summary job must declare every specialist in its needs: list and must inspect needs.*.result before writing its output:
summary:
needs: [plan, test-impact, security-impact]
if: always()
steps:
- name: Verify all specialists completed
run: |
if [[ "${{ needs.test-impact.result }}" != "success" \
|| "${{ needs.security-impact.result }}" != "success" ]]; then
echo "Partial completion — do not treat as full success"
exit 1
fi
Starting the final summary without all needs fans-in prematurely and publishes an incomplete verdict. Letting each agent rewrite the same PR comment concurrently causes race conditions and lost findings — each specialist should append to its own comment or artifact; only the fan-in job writes the aggregate. Using if: success() on the summary silently skips it when any specialist fails; using if: always() without inspecting needs.*.result treats failures as passes. A skipped specialist (because its if: condition was false) is not a passing specialist — it produced no evidence. Merging on the first specialist’s success defeats the entire gate.
Planner / Implementer / Reviewer Roles and Sequencing
The planner/implementer/reviewer triad is the sequential counterpart to fan-out. The planner decomposes scope and emits a plan artifact; the implementer consumes that plan and produces code changes plus a decision log; the reviewer (often an audit job) validates the diff against the plan and uploads evidence. Each role has a strict predecessor.
jobs:
planner:
outputs:
plan-path: ${{ steps.plan.outputs.path }}
steps:
- id: plan
run: echo "path=plan.json" >> "$GITHUB_OUTPUT"
- uses: actions/upload-artifact@v4
with: { name: agent-plan, path: plan.json }
implementer:
needs: planner
steps:
- uses: actions/download-artifact@v4
with: { name: agent-plan }
reviewer:
needs: implementer
steps:
- uses: actions/download-artifact@v4
with: { name: agent-plan }
Running reviewer before implementer completes — by omitting needs: implementer — lets it evaluate a stale or empty workspace. If reviewers added acceptance criteria to the issue after the plan was produced, the implementer must re-read the issue, not resume from the last unsaved chat context. Decisions living only in ephemeral chat evaporate; upload them to the issue and draft PR. An audit job typically runs after the reviewer with if: always() to capture the full decision log regardless of upstream outcome.
Handoff State Passing Across Jobs
State moves between jobs through three mechanisms:
| Mechanism | Use for | Persistence | Size |
|---|---|---|---|
$GITHUB_OUTPUT + jobs.<id>.outputs | Small scalars: file paths, versions, decision flags | Per-run, in run metadata | ~1 MB per output |
actions/upload-artifact → download-artifact | Plans, logs, state.json, agent-state, evidence bundles | 90 days (configurable) | GB-scale |
| Repository commits / PR comments / issue body | Human-auditable decisions | Permanent | Unlimited |
The correct pattern is to emit a file path as a job output, then upload the file as an artifact, so downstream jobs can download-artifact deterministically:
- id: emit
run: |
echo '{"decision":"proceed"}' > state.json
echo "state-path=state.json" >> "$GITHUB_OUTPUT"
- uses: actions/upload-artifact@v4
with:
name: agent-state
path: ${{ steps.emit.outputs.state-path }}
Uploading agent-state and downloading agent_state yields an empty workspace and a green job that produced nothing. Downstream jobs must parse state.json and branch on it, not assume success. If the implementer downloads agent-plan from a prior run instead of the current run, state.json reflects yesterday’s scope — always download from the current run unless cross-run continuation is explicit.
When retiring an agent, the retired profile’s historical artifacts must remain downloadable for audit, while new invocations route to the replacement.
Critical rules: Every fan-in job must list all specialists in needs: and inspect needs.*.result — distinguish success, failure, cancelled, and skipped. One writer per summary surface. Emit file paths via $GITHUB_OUTPUT, persist content via upload-artifact, and use identical artifact names between upload and download jobs. Durable artifacts — issue body, PR comments, workflow artifacts — are the only auditable evidence.
Evidence, Observability, and Traceability
Agentic workflows must produce evidence that is inspectable after the fact — not just at the moment of execution. Evidence is what allows a reviewer, auditor, or on-call engineer to reconstruct what the agent did, why it did it, and whether it was authorized. Three surfaces carry this evidence: structured artifacts uploaded to workflow runs, step summaries written to $GITHUB_STEP_SUMMARY, and PR comments that link the workflow run to the human-readable change.
Structured Evidence Artifacts
Every agent run should produce at minimum two artifact types: a checks artifact (checks.json) containing the results of all automated validations (unit tests, integration tests, security scans), and a review-signals artifact (review-signals.json) containing qualitative reviewer assessments (backward compatibility, API contract adherence, scope alignment). Neither artifact alone is sufficient for a merge decision.
checks.json is quantitative — it proves that specific automated criteria passed or failed. review-signals.json is qualitative — it captures what automation cannot measure. An evaluation summary job must consume both via the needs context and emit a combined verdict. It must run even when upstream validation jobs fail, which is why if: always() is used on evidence-gathering steps and why the summary job depends on upstream jobs without requiring their success.
A typical workflow skeleton:
on:
pull_request:
branches: [main]
jobs:
behaviour-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test -- --grep behaviour
evaluation-summary:
needs: [behaviour-tests]
if: always()
runs-on: ubuntu-latest
steps:
- run: ./scripts/write-summary.sh
The three exam-tested slots are pull_request (trigger), run (execute checks), and either success() (only summarise on pass) or always() (preserve evidence on failure). Choose based on intent: success() for gating summaries; always() for evidence retention.
Traceability to the Originating Issue
Every agent-produced artifact must trace back to the issue that authorized the work. The issue body is the contract; the PR description is the implementation record; the workflow artifacts are the evidence. A PR that cannot be traced to an issue is ungoverned work. The durable-artifact loop the exam tests is: (1) create/update the issue with scope and acceptance criteria → (2) capture implementation decisions in the draft PR description or comments → (3) upload validation evidence as a workflow artifact → (4) review the durable artifacts before marking the PR ready.
Continuing from an unsaved chat context or merging when “the agent says the task is complete” are both wrong — chat context is ephemeral and the agent’s self-report is not an authoritative signal.
Step Summaries and PR Annotations
$GITHUB_STEP_SUMMARY accepts Markdown and renders in the Actions run UI. It is the correct surface for structured agent plans, validation summaries, and evaluation verdicts. A step that writes to $GITHUB_STEP_SUMMARY does not require any additional permissions: beyond contents: read.
PR annotations (inline comments on specific lines) require pull-requests: write. They are appropriate for pinpointing specific findings (e.g., a security scanner result on line 42 of a file) but are not a substitute for the artifact record — annotations are not downloadable and do not persist beyond the PR’s lifetime.
Critical rules: PR creation is not completion. Counting PRs, or trusting the agent’s own summary, proves nothing. Behaviour tests plus reviewer signals plus a summary artefact are the minimum evidence set. Classify failures before changing prompts or tools — reasoning, environment, permission, and orchestration failures each have distinct fixes. checks.json is quantitative, review-signals.json is qualitative — neither alone is sufficient, and security/code-scanning results belong in the quantitative set.
Evaluation Frameworks and Failure Taxonomy
Evaluation is the discipline of measuring whether an agent’s output actually satisfied the intent expressed in the originating issue — not just whether automated checks passed. The exam distinguishes between presence of criteria (the issue contains acceptance criteria), validation of criteria (a workflow checks that the PR includes the required artifacts), and satisfaction of criteria (behaviour tests prove the change does what was asked).
Evaluation Maturity and Intent Measurement
The maturity order the exam tests is: (1) define acceptance criteria in the issue → (2) add qualitative labels (backward compatibility, API contract, scope alignment) → (3) add quantitative signals (unit tests, integration tests, security scans) → (4) tune prompts and instructions based on failed evaluation cases. Skipping to prompt tuning without first establishing criteria and signals is always wrong.
Intent measurement requires both quantitative and qualitative signals. Quantitative signals include unit tests, integration tests, behaviour-focused tests, and security scanning (code scanning must be included — ignoring it leaves the security dimension of intent unmeasured). Qualitative signals capture what automation cannot: did the reviewer judge that backward compatibility was preserved, that the API contract was honoured, that the change was in scope of the issue?
Drift reports — where the agent’s changed files diverge from the issue scope declared in the contract — must be produced and compared against the contract. Skipping the drift-vs-scope comparison is how requirement misunderstandings slip through green checks.
Failure Categorization and Remediation Selection
When an agent fails, the exam demands you classify before you fix. Changing prompts, enabling more tools, or re-running the agent before diagnosing the failure class is always wrong.
| Failure class | Signature | Correct remediation |
|---|---|---|
| Reasoning error | Agent had all context and tools, but drew wrong conclusion | Refine prompt, add examples, tighten contract |
| Setup / environment failure | Devcontainer, dependencies, or repo trust missing | Start Copilot CLI inside a trusted repository; fix env |
| Permission / tool error | MCP server configured but tool not exposed to the agent | Grant the specific tool; do not enable everything |
| Orchestration issue | Jobs run in wrong order, missing needs, path filter blocks required check | Fix workflow topology |
When a tool is configured at the server level but unavailable to a custom agent (for example, a Sentry MCP server is present but the Sentry tool is not exposed), the root cause is a tool-permission issue, not a reasoning error. Any answer the agent guesses in that state must be treated as lower confidence because required external context was unavailable. When Copilot CLI fails to plan inside an untrusted directory, the fix is not to rewrite the prompt — it is to start the CLI inside the trusted repository so tools become available.
Enabling all tools to paper over a missing permission is a specifically called-out anti-pattern: it hides the classification signal and expands the blast radius. Not persisting evidence on failure (omitting if: always()) destroys the data needed to classify the next occurrence.
Custom Agents and Profiles
Custom Copilot agents in GitHub are declared as profile files that combine YAML frontmatter with instruction bodies. The frontmatter is the security and routing contract: it tells the platform which model provider backs the agent, which tools the agent can call, whether humans can invoke it directly, and whether the base model is allowed to hand work off to it automatically.
Agent Profile Frontmatter and Tool Scoping
Every custom agent profile begins with a frontmatter block that defines the agent’s identity and capabilities:
---
name: ci-remediation-agent
description: Fixes broken GitHub Actions workflows
provider: github-copilot
tools: [read, search, edit, github/*]
user-invocable: true
disable-model-invocation: false
---
The provider: github-copilot field pins the agent to the Copilot backend. Any deviation causes the platform to fall back to defaults, which removes deterministic routing.
The tools: array is a positive allow-list. Each entry is a capability the agent may exercise. Common built-in tools include read (file reads), search (repo search), and edit (write file changes). External or MCP-provided capabilities are namespaced, such as github/* for the GitHub MCP server or jira/create-issue for a scoped tool. If the profile declares MCP server bindings but leaves the tools list empty, the agent has zero callable capabilities — the MCP registration is inert. Granting * or a broad github/* when the agent only needs github/issues.read violates least privilege.
The user-invocable flag controls whether a human can type @agent-name in a PR or issue. The disable-model-invocation flag controls whether the base Copilot model may automatically delegate to this agent. These two switches are independent. Setting user-invocable: false alone still leaves the agent reachable via automatic model routing; setting disable-model-invocation: true alone still allows manual @ mentions. To fully quiesce an agent you must set both.
If a profile lists a tool like shell or bash for convenience during prototyping, the agent inherits arbitrary command execution rights. The exam expects shell to be omitted unless explicitly required. When validating a new profile, you must invoke it explicitly (@ci-remediation-agent fix this workflow) rather than a generic Copilot chat, otherwise the tool scoping you configured is not exercised.
Retiring or Deprecating Agents Without Breaking History
Agents accumulate audit evidence: PRs they authored, issues they commented on, workflow logs referencing their name, and required status checks keyed to their identifier. The correct retirement sequence, in this exact order:
- Add a deprecation notice to the retired agent profile. This is a human-readable marker in the profile body announcing the sunset and pointing to the replacement.
- Disable manual and model invocation by setting
user-invocable: falseanddisable-model-invocation: true. This stops new work without deleting the profile, preserving the name as a resolvable reference for historical artifacts. - Update routing docs, issue templates, CODEOWNERS, and workflow documentation to point at the replacement agent.
- Verify existing PRs, issues, and artifacts remain traceable — the agent’s prior outputs must still resolve to a valid (if disabled) profile so audit trails don’t dangle.
Deleting draft PRs destroys audit evidence, removing workflow logs and artifacts violates compliance retention, and deleting the profile outright breaks required-check references. If a branch protection rule references workflow-fix-agent / verify as a required status check, deleting the profile leaves the rule unsatisfiable and blocks merges indefinitely.
Coding Agent Setup Steps (copilot-setup-steps)
When Copilot’s coding agent begins work on a repository, it can execute a pre-flight workflow to install dependencies, authenticate to private registries, and stage the environment. This workflow lives at a fixed path and uses a fixed job name — both are non-negotiable:
# .github/workflows/copilot-setup-steps.yml
name: Copilot Setup Steps
on:
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml
jobs:
copilot-setup-steps:
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install private deps
working-directory: ./service
run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
| Field | Required value | Why |
|---|---|---|
| File path | .github/workflows/copilot-setup-steps.yml | Copilot only looks here |
| Job name | copilot-setup-steps | Copilot matches by job id |
| Registry permission | packages: read | Needed to pull private packages |
If the job name is anything else (setup, bootstrap, install), Copilot cannot detect the setup steps and will start work in an unprepared environment. If packages: read is missing, private dependency installs fail with 401/403. The push and pull_request triggers scoped to the workflow file’s own path make the workflow testable when changed. The correct working-directory matters when the project is not at the repo root. Keep permissions minimal: contents: read and packages: read are typically the entire required set.
MCP Servers, Registry Policy, and Tool Enablement
Model Context Protocol (MCP) servers extend the Copilot agent’s capabilities beyond the built-in GitHub tools. Governance of MCP servers operates at two levels: the enterprise/organization registry (which servers are approved for use) and the agent profile (which tools from an approved server the agent may call).
Registry-Only Enforcement and Server IDs
An MCP registry is a manifest that lists approved server IDs. The registry URL alone does not enforce anything — you must also set Restrict MCP access = Registry only in the enterprise or organization policy. Without this setting, users can connect to any MCP server regardless of the registry.
The correct rollout order is: (1) inventory all MCP servers currently in use; (2) publish the approved registry manifest; (3) configure the registry URL in enterprise/org settings; (4) set Restrict MCP access to Registry only. Leaving the policy at “Allow all during rollout” defeats the entire purpose — the exam treats that as an unambiguous failure mode.
Server enforcement is done by exact server ID match against the registry manifest. If a developer installs a server whose ID differs — even by casing or a version suffix — from the ID published in the registry, the server is treated as unlisted and blocked under Registry-only policy. This applies equally to local MCP servers (stdio servers running on the developer’s machine): they must still be added to the registry with the exact server ID the local install advertises. There is no “local servers are automatically trusted” exception.
The IDE or CLI itself will not refuse unlisted servers by default — enforcement is a policy decision made by GitHub against the registry. If the enterprise has not set Registry-only, the surface will happily connect to anything the user configures.
Secrets and Authentication for MCP Tools
MCP tools for the Copilot cloud coding agent authenticate using Agent secrets (also called Copilot agent secrets), configured at the repository, environment, or organization level under Copilot settings — not as Actions secrets. Storing an MCP token as an Actions secret is a distractor answer: Actions secrets are not injected into the Copilot agent runtime, and the server will fail to start with a missing-credential error.
Any secret referenced from an MCP server configuration must be named with the COPILOT_MCP_ prefix. For example, a Sentry MCP server config would reference COPILOT_MCP_SENTRY_TOKEN, and a Notion server would reference COPILOT_MCP_NOTION_TOKEN. The prefix is what allows Copilot to identify and inject the secret into the MCP process environment. A secret named SENTRY_TOKEN — no prefix — is silently ignored, and the server fails at startup.
Never embed a raw personal access token (PAT) inside the MCP JSON configuration. For remote MCP servers used by the cloud coding agent, the supported pattern is a remote HTTP MCP server entry whose authentication uses a token reference to a COPILOT_MCP_-prefixed secret. OAuth-only remote MCP servers are not supported for the Copilot cloud agent — if a remote server offers only OAuth (no bearer/token option), it cannot be used from the cloud agent.
The built-in GitHub MCP server is provisioned automatically for the Copilot coding agent. You should not re-add it manually as a custom MCP entry. You may, however, customize its behavior: a repository admin can supply a broader token (via a COPILOT_MCP_-prefixed secret) if the default current-repository token is too narrow, and can restrict which built-in GitHub MCP tools the agent is permitted to call via the tools allow-list.
Tool Lists and Enablement Discipline
MCP configuration in an agent profile lives under the mcp-servers: key. Each server entry must declare a tools: list. Two forms exist, and they behave very differently:
tools value | Behavior |
|---|---|
["*"] | Enables every tool the server exposes — including ones added later by server updates |
[] | Server is configured but no tools are enabled — effectively disabled |
["tool_a","tool_b"] | Explicit allow-list; only listed tools are callable |
Using ["*"] is treated as a misconfiguration in security-sensitive scenarios because it silently grants any newly-added tool. An empty tools: [] is a subtle bug: the server appears configured, connects, and shows up in logs, but the agent cannot actually invoke anything on it. If an exam scenario says “the server is configured but the agent cannot use the required capability,” look first for the tool missing from the tools list.
A complete custom agent profile snippet for the cloud coding agent:
name: workflow-debugger
type: github-copilot
disable-model-invocation: true
mcp-servers:
github:
tools: ["get_issue", "list_pull_requests", "get_pull_request_files"]
The agent type is github-copilot (for Copilot cloud agent tasks on GitHub.com); the tool enablement key is tools; and disable-model-invocation: true makes the agent available for deliberate user selection but prevents automatic model routing to it.
Critical rules: Registry URL alone does not enforce anything — you must also set Restrict MCP access = Registry only. Server IDs must match exactly between install and registry, including for local MCP servers. MCP secrets use the COPILOT_MCP_ prefix and are stored as Agent secrets, never as Actions secrets. Never inline a PAT in MCP JSON. OAuth-only remote MCP servers are not supported by the Copilot cloud agent. The built-in GitHub MCP is auto-provisioned — do not re-add it manually. tools: [] disables the server; tools: ["*"] enables everything — use explicit allow-lists in production.
Issue and Planning Processes
The issue is the canonical contract between a human requester and the Copilot coding agent. Everything downstream — the agent session, the draft pull request, the validation workflow, and the merge decision — must trace back to explicit, machine-readable fields captured before the agent is ever assigned.
Issue Templates and Forms (Scoping and Constraints)
An agent-ready issue must eliminate ambiguity before Copilot is assigned. A well-designed issue form (.github/ISSUE_TEMPLATE/agent-task.yml) forces the requester to fill five mandatory fields:
| Field | Purpose |
|---|---|
| Expected result | The concrete, verifiable outcome (not “make it faster”) |
| Excluded actions | Explicit forbidden operations (e.g., “do not apply Terraform,” “do not modify auth”) |
| Affected files | Repository paths the agent is authorized to touch |
| Validation evidence | The checks, artifacts, or plan output that proves success |
| Approval path | Who reviews, and under what branch protection |
The correct four-step pattern for converting a vague request into an agent-ready issue is: (1) identify service scope, approval path, and operational risk → (2) define expected outputs and affected paths → (3) add success evidence such as plan output and required checks → (4) convert the completed request into an agent-ready GitHub Issue. Assignment to Copilot happens after these fields are enforced by the form’s validations: required: true constraints.
“Make it faster” or “fix the port scheduler infrastructure” fails because there is no expected result and no acceptance check — the agent has no exit condition. Asking Copilot to infer forbidden actions from Terraform logs inverts the model: exclusions must be human-declared constraints, not agent-inferred guesses. Assigning Copilot before form fields are enforced means the issue-as-contract has no teeth. Allowing Copilot to apply infrastructure if tests pass conflates validation with authorization.
Plan Schema and Plan-First Workflows
A plan-first workflow requires the agent to produce and commit an agent-plan.json before any implementation begins. The plan schema the exam tests contains five required fields: goal, scope, files_to_change, tests, and risks. A plan that omits risks is incomplete even if the other four fields are present.
Plan validation is a required check: a workflow triggers on pull_request, parses agent-plan.json for the required fields, and fails if any are missing. This check must run before the implementation workflow is allowed to proceed. Plan drift — where the agent’s changed files diverge from files_to_change in the plan — must be detected and reported. A drift report that is not compared against the contract is useless.
Planning vs. Execution Labels and Routing
Labels stage autonomy — they do not confer it. Three labels dominate:
| Label | Purpose | Effect |
|---|---|---|
agent-plan | Requests impact analysis; no code changes | Triggers a planning workflow that comments on the issue |
agent-execute | Authorizes implementation on an approved plan | Triggers execution workflow that may open a draft PR |
agent-ready | Signals the issue is well-formed for the agent | Triggers agent dispatch when configured |
A planning workflow reacts to issues: labeled with label agent-plan, needs issues: write to comment, and must not create branches or PRs. The most common failure of a plan workflow is a missing write scope on the token — configured with issues: read or permissions: {} instead of issues: write.
Labels do not grant merge rights. Branch protection status checks remain authoritative; an agent-execute label authorizes the agent to open a draft PR, but the PR still requires reviews and passing checks. Labels also do not automatically start an agent when the workflow is configured for workflow_dispatch only — if a maintainer labels an issue agent-ready and no run appears, the correct diagnosis is that the workflow only supports manual dispatch.
Always record the trigger user — github.event.sender.login or github.event.label.creator — in the workflow output or an audit comment. Not recording who approved readiness is a governance failure.
Critical rules: Durable artifacts — issue body, PR comments, workflow artifacts — are the only auditable evidence. Chat context and agent self-reports are not. Label-triggered workflows require on: issues: types: [labeled]; workflow_dispatch alone will not fire on label events. Instructions files do not prove plan adherence — only a committed, validated agent-plan.json does.
Permissions and Security Hygiene
Workflows must grant exactly the token scopes they need, no more, and secrets must be gated behind human approval whenever untrusted code — including code from forks or from agents — could execute alongside them.
Commenting and Metadata Writes from Workflows
The default GITHUB_TOKEN in modern repositories is read-only. Any workflow step that posts a PR comment, updates a check, labels an issue, or edits PR metadata will fail unless the workflow explicitly elevates the token via the permissions: block.
To post a comment on a pull request — whether via gh pr comment, the REST API, or a guardrail action — the workflow must declare pull-requests: write. For issue comments and labels it must declare issues: write. contents: read is not sufficient; contents governs repository file access (checkout, blob reads), not PR conversation writes.
permissions:
contents: read
pull-requests: write
jobs:
guardrail:
runs-on: ubuntu-latest
steps:
- run: gh pr comment "$PR" --body "Policy check: OK"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
A guardrail comment step must not fail the job early. If policy evaluation is meant to inform reviewers (a soft signal that feeds into human judgment or a downstream summary job), using exit 1 on violation short-circuits later jobs like the acceptance-test evaluator and destroys the intent signal reviewers rely on. Use continue-on-error: true or if: always() on the comment step so the guardrail annotates but does not gate. Never use broad Personal Access Tokens (PATs) in terminal sessions — use the ephemeral GITHUB_TOKEN with narrow permissions: grants instead.
Code Scanning and SARIF Permissions
Uploading SARIF (Static Analysis Results Interchange Format) results to GitHub’s code-scanning API requires the security-events: write permission. This is a distinct scope from contents or pull-requests; forgetting it is the number-one cause of failed CodeQL and third-party scanner uploads.
on:
pull_request:
branches: [main]
merge_group:
permissions:
contents: read
security-events: write
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
- uses: github/codeql-action/analyze@v3
The merge_group trigger is mandatory when the same required check must run for merge-queue entries. A workflow that only triggers on pull_request will not produce a check run for merge-queue commits, and branch protection will either block indefinitely or allow a merge without re-verification. Do not skip scans for bot PRs (including Copilot coding-agent drafts) — Copilot’s PRs are exactly the ones you most want scanned before promotion.
Secrets Handling and Fork Approvals
Workflows triggered by pull_request from a fork do not automatically run; a maintainer must approve the first run for new contributors, and organizational policy can require approval for every fork run. Without this gate, a malicious fork could exfiltrate any secret the workflow references.
When a job declares environment: prod, the environment’s protection rules — including required reviewers — evaluate before secrets are injected into the job. A common wrong pattern places terraform apply -auto-approve in a job that references environment secrets but is triggered automatically on push; the auto-trigger bypasses the manual gate.
The correct pattern for a manually triggered, protected-environment Terraform apply:
on:
workflow_dispatch:
jobs:
apply:
runs-on: ubuntu-latest
environment: port-prod
steps:
- run: terraform apply -auto-approve
workflow_dispatch (manual trigger only), port-prod (the protected environment name), and terraform apply -auto-approve (the command that runs after reviewers approve and secrets are made available) are the three exam-correct values. The -auto-approve flag on Terraform is not a security bypass here — the human approval already happened at the environment gate.
When calling a reusable workflow, secrets are not inherited by default. Use secrets: inherit deliberately, or pass named secrets explicitly. For calling out to external processes with a curated allow-list, --secret-env-vars scopes exactly which secrets cross the boundary.
Critical rules: The default GITHUB_TOKEN is read-only. PR comments need pull-requests: write; issue comments need issues: write; SARIF uploads need security-events: write. Add merge_group alongside pull_request whenever a required check must also gate the merge queue. Guardrail comment steps must not fail the job early. Environment protection rules gate secret injection. Fork PRs require approval before running.
Memory, Context, and Resume Strategy
Memory and context management is where agentic workflows fail most often — not from bad code generation, but from stale assumptions. This domain covers how to choose the right context surface, prevent drift across long sessions, and invalidate memory that has outlived its usefulness.
Choosing the Right Memory/Context Mechanism
GitHub Copilot exposes several distinct context surfaces, each with a different lifetime and scope:
| Surface | Lifetime | Scope | Best for |
|---|---|---|---|
| Task-local context (chat, prompt) | Single task/session | The current turn | One-off details, ephemeral scratch data |
| Copilot Memory facts | Persistent, cross-session | User/repo | Stable personal or team preferences |
| Repository custom instructions | Persistent, versioned | Whole repo | Stable build rules, project conventions |
| Path-specific instructions | Persistent, versioned | Files matching a glob | Framework or language rules by directory |
| Copilot Spaces | Curated, task-focused | Selected files, docs, issues | A focused workstream (e.g., a migration) |
| MCP tools / external APIs | Live, on-demand | Runtime call | Feature flags, deploy state, live data |
The decision rule: stable and repo-wide → repository instructions; stable but path-scoped → path-specific instructions; task-scoped and curated → Copilot Space; live/mutable state → external tool via MCP, never baked into instructions.
Stable build rules belong in repository custom instructions because they apply everywhere and rarely change. React guidance belongs in path-specific instructions (targeting web/forms/** or src/**/*.tsx) because it applies only to matching files. Live feature-flag state must never be copied into instructions — it goes stale the moment a flag flips. Instead, the agent should fetch it at runtime through an MCP tool or API call.
Copilot Spaces are the correct answer when you need a curated, task-focused knowledge boundary — for example, an “analytics migration” Space that pins the relevant issue, design doc, and current source files. They isolate the working set so unrelated repo history does not bleed into the agent’s reasoning.
When configuring an MCP server in an IDE, never embed a PAT inside the JSON; authentication should flow through the IDE’s credential handling or OAuth. Hard-coded tokens in configuration files are a security failure.
Context Drift Mitigation and Resumability
Drift happens when the agent’s internal picture of the world diverges from the repository’s actual state. Long sessions, paused work, and multi-run workflows all accumulate drift. Mitigation depends on re-anchoring to authoritative sources at every resume point.
| State signal | Resume action |
|---|---|
| Base branch may have moved | Fetch latest base and rebase/merge |
| Triage scope may not match diff | Compare accepted scope vs. current diff |
| Reviewer feedback may be new | Re-read updated PR comments |
| Original intent may be lost | Re-anchor to the linking issue |
Recording decisions in the issue or PR body — original issue link, accepted scope, changed files, validation commands — creates a durable PR checklist that reviewers use to detect drift before merge. Neither repository custom instructions nor a PR checklist guarantees Copilot cannot modify unrelated files. That guarantee requires branch protection, CODEOWNERS, and diff review.
For multi-job workflows, the context bundle pattern packages the current issue, linked PR, latest commit SHA, and failing check summary as an artifact. To avoid picking up a stale artifact from a previous run, the artifact name must be scoped to the current run ID:
- name: Upload context bundle
uses: actions/upload-artifact@v4
with:
name: agent-context-${{ github.run_id }}
path: context/
# later job
- name: Download context bundle
uses: actions/download-artifact@v4
with:
name: agent-context-${{ github.run_id }}
The github.run_id suffix guarantees run-scoped freshness. Reusing a latest tag or an unversioned name is the trap — it lets a prior run’s bundle satisfy the current job.
Stale Memory and Cache Invalidation
Persistent memory is only valuable when it is currently true. The correct remediation sequence when legacy memory contaminates unrelated work is: (1) review stored Copilot Memory facts; (2) delete misleading repository memory facts; (3) add valid conventions to path-specific instructions; (4) create a Copilot Space for the current focused task. Moving billing notes into repository-wide instructions widens the blast radius; excluding the billing package entirely destroys valid guidance.
Broad cache keys and latest-tagged artifacts restore outdated state across runs. Cache keys must include content hashes and, when appropriate, the run_id. Incident data, feature-flag snapshots, and any live state must not persist beyond the task scope in which they were fetched.
When memory or instruction files change, a memory evaluation workflow must run before merge. The workflow triggers on pull_request events scoped to instruction and memory file paths, requires pull-requests: write to comment results, and executes via run: steps. This closes the loop: every change to persistent context is validated against the evaluation suite before it can influence future agent runs.
Critical rules: Stable + repo-wide → repository instructions; stable + path-scoped → path-specific instructions; task-scoped → Copilot Space; live state → MCP tool, never instructions. Never bake live data into custom instructions or memory. Scope workflow artifacts with agent-context-${{ github.run_id }} to prevent cross-run staleness. Resume by re-anchoring: fetch latest base, re-read the issue, compare triage scope to diff, re-check reviewer comments.
Copilot CLI Safe Operation and Policy Hooks
The Copilot CLI is a powerful agentic tool that can read, write, and execute against your local filesystem and remote services. Because it operates with the same permissions as the invoking shell, disciplined operation and enforceable policy hooks are the primary controls that prevent scope creep, secret leakage, and unauthorized production changes.
Safe Working Directory and Scope Discipline
The single most important operational rule is: always launch Copilot CLI from the root of the specific repository you intend to modify, on a trusted, freshly synchronized checkout. The working directory defines the agent’s implicit scope — files above or beside it may become visible to tool calls, and files inside it may be mutated. Starting the CLI from ~ (home) or ~/Downloads is an exam trap: those directories typically contain SSH keys, browser-downloaded artifacts, credentials, personal notes, and multiple unrelated repositories.
Before invoking copilot, the working tree should be clean and intentional. Run git fetch and git pull to synchronize with the current remote state (skipping this step causes the agent to plan against stale code and produce merge-conflicting diffs), stash or commit unrelated uncommitted changes so refactors do not accidentally sweep them into the same PR, and remove loose local files — scratch notes, TODO.md, exported logs, .env.local copies — that could influence the agent’s reasoning.
| Wrong practice | Why it fails |
|---|---|
Launch from ~/Downloads | Exposes unrelated files, credentials, other repos |
Skip git fetch before session | Agent plans on stale code, produces broken diffs |
Leave local notes.md in tree | Notes influence unrelated refactors |
| Rely on prompt to “not touch X” | Prompts do not enforce filesystem boundaries |
| Mix two features in one session | Unreviewable PR, poor audit trail |
PreToolUse Hooks and Guardrails
Copilot CLI supports repository-scoped hook configuration that intercepts tool invocations before they execute. The preToolUse hook is the enforcement point: it receives the proposed tool call, evaluates it against a policy script, and either allows, blocks, or escalates. A well-designed preToolUse hook does three things simultaneously:
- Display policy guidance to the user at session start and on each intercepted call, so the developer sees what is permitted.
- Log prompts and tool calls to a durable audit sink (a file, syslog, or SIEM) so reviewers can reconstruct sessions.
- Block risky commands with a clear escalation reason, telling the user why it was blocked and how to request approval — not a silent denial.
hooks:
preToolUse:
- script: .github/copilot/policy.sh
onBlock: "Command blocked by org policy. See #platform-approvals to escalate."
logPromptsTo: .copilot/audit.log
Production-modifying operations — terraform apply against prod workspaces, kubectl against prod contexts, npm publish, gh release create, direct pushes to protected branches, rm -rf on tracked paths — must be blocked pending human escalation. Silent blocking is equally wrong: guardrail messages must be explicit so the developer knows the boundary and the escape route.
Hook scripts themselves are reviewed, version-controlled, and approved by the platform team — a developer cannot self-authorize a permissive hook. Without a prompt log, post-incident audits cannot determine what the agent was asked to do, only what it did. Both the prompt and the tool call must be captured.
Terminal-to-PR Evidence and Secret Env Handling
Terminal output is ephemeral. The exam expects that after an agentic CLI session, you convert the transcript, decisions, and diffs into a PR artifact — typically by committing an audit bundle (audit/session-<id>.md containing the prompt, policy decisions, tool calls, and results) and posting a summary as a PR comment via gh pr comment. Persisting only in the terminal is a listed trap: once the shell closes, the evidence is gone.
The Copilot CLI supports the --secret-env-vars flag, which designates specific environment variables as sensitive. Values of these variables are masked in transcripts, logs, and any generated summary or PR comment:
copilot --secret-env-vars "GITHUB_TOKEN,NPM_TOKEN,AWS_SECRET_ACCESS_KEY" \
--prompt "Prepare release notes and open a draft PR"
Without --secret-env-vars, secret values can appear in the transcript that gets assembled into the audit package and posted publicly on the PR. The audit package assembly flow is: capture prompt → capture policy decisions from preToolUse → capture tool calls and diffs → apply secret masking → write to audit/ directory → commit → gh pr comment --body-file audit/summary.md.
Critical rules: Always launch Copilot CLI from the trusted target repository root on a freshly git fetch/pull’d checkout. One task, one clean working tree, one PR. Prompts do not enforce boundaries — the filesystem working directory and preToolUse hooks do. A compliant preToolUse hook must do all three: show policy guidance, log prompts for audit, and block risky commands with an explicit escalation reason. Hooks must be org-approved and version-controlled. Use --secret-env-vars to mask sensitive variables before any transcript is assembled into an audit bundle or PR comment.
Conflict Management and Isolation
When multiple agents — Copilot coding agent tasks, human contributors, and automation — operate concurrently against the same repository, the platform must guarantee that their work does not corrupt each other’s evidence, cancel each other’s runs, or land in an inconsistent order.
Concurrency Groups and Run Isolation
The concurrency: key in a workflow controls whether two runs of that workflow can execute simultaneously. Runs that resolve to the same group name are serialized; if cancel-in-progress: true, an incoming run cancels the current one in that group.
The concurrency group must uniquely identify the unit of work being isolated — typically the pull request or the ref:
concurrency:
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
This gives each PR its own lane. If you write group: ci (a static string), every PR shares one lane, and each new push cancels a peer PR’s run. Two Copilot agents working in parallel would repeatedly kill each other’s checks. Concurrency groups must be parameterized per unit of work, not per workflow.
Matrix strategies with fail-fast: false preserve per-scope evidence; the default fail-fast: true cancels sibling matrix legs when one fails, destroying the evidence from the surviving legs. Every service and every PR must get uniquely-named artifacts — shared artifact names silently overwrite evidence from concurrent runs.
Overlap Detection and Coordination Reports
When two agent PRs modify the same files, the platform needs a mechanism to detect the overlap before either PR merges. An overlap-detection workflow triggers on pull_request, reads the changed file list, compares it against open PRs, and uploads a coordination artifact listing the conflicts. This workflow uses only read permissions and actions/upload-artifact@v4 — no write scopes are needed for detection.
The coordination artifact is mandatory for diagnostics: without it, the overlap is invisible in the workflow logs. Shared-ownership conflicts (two agents both modifying src/auth/) must be resolved by choosing a canonical path — the PR that owns the shared module lands first, and dependents rebase against it. Never resolve by test coverage or diff size; resolve by dependency direction.
CODEOWNERS must route shared modules (auth, workflows, shared libs) to platform owners so agent-only reviews cannot merge cross-cutting changes. An agent PR that touches a CODEOWNERS-governed path cannot merge without the listed owner’s approval, regardless of how many other checks pass.
Merge Queue Conflicts and Sequencing
The merge queue serializes PRs into a single ordered stream and re-runs required checks on the combined state (the queued PR merged with all PRs ahead of it in the queue). This catches integration failures that individual PR checks miss. For the merge queue to work, required checks must include the merge_group trigger — a check that only runs on pull_request will not run on merge-queue entries.
When dependent PRs are queued (an implementation PR and its test PR), they must be sequenced, not batched. Queuing them together causes the combined-context failures the queue is designed to catch, so the implementation must land before the test that assumes it. On merge-queue failure: pause the queue, inspect merge-group logs, require CODEOWNER review, then re-enable. Never disable required checks to force a merge.
Every merge-queue failure must refresh context: rebase against the target branch after reconciliation, re-run checks, and reread the original issue acceptance criteria before re-queuing.
Critical rules: Concurrency groups must be parameterized per PR. fail-fast: false on matrix strategies preserves per-scope evidence. Every service and every PR gets uniquely-named artifacts. Overlap-detection workflows use on: pull_request, permissions: read, and actions/upload-artifact@v4. Resolve shared-ownership conflicts by dependency direction, not diff size. On merge-queue failure: pause, inspect, require CODEOWNER review, then re-enable. Sequence dependent PRs by dependency direction.
Risk, Path Classification, and Labeling
This domain governs how an agentic workflow decides what an AI-generated change is allowed to do, where it is allowed to act, and who must intervene before it proceeds.
Path-Based Risk Classification and Auto-Labeling
Path-based risk classification is the mechanism by which a workflow inspects the files touched by a Copilot-generated pull request and applies a label reflecting the blast radius of the change. The canonical mapping:
| Path glob | Risk tier | Label applied |
|---|---|---|
docs/** | Low — documentation only | agent-low-risk |
src/auth/** | High — authentication surface | human-review-required |
infra/deploy/** | High — deployment / infrastructure | human-review-required |
compliance/** | High — regulatory artifacts | human-review-required |
A typical auto-labeling workflow triggers on pull_request (not push or issues), reads the changed file list, matches it against these globs, and applies exactly one of two labels: agent-low-risk for docs-only diffs, or human-review-required when any high-risk path is touched.
Running an approval gate unconditionally for every PR is wrong — approval gates must be scoped by risk, otherwise low-risk documentation changes are needlessly blocked and reviewer fatigue erodes the value of the gate. “Low risk” does not mean “no validation” — a docs-only PR still needs evidence: the workflow must run, apply the agent-low-risk label, and record that classification occurred. High-risk paths must be mapped to owners — a human-review-required label without a CODEOWNERS entry or routing rule is decorative, not enforcing.
If agent-plan-check / check-plan is required by branch protection but the workflow uses paths: filters, a PR that changes only implementation files will cause the workflow to be skipped. A skipped required check remains pending forever — it never fails, so the PR cannot merge and cannot be diagnosed without knowing this mechanic. The fix is to either remove the path filter from the required check or emit a synthetic success/failure job that always runs.
Repository Identity Guards for Validation
A repository guard is a defensive check at the top of a workflow that verifies github.repository matches the expected owner/name and fails immediately otherwise. This prevents a workflow that was designed for one repository from silently succeeding after being copied — via fork, template, or paste — into another.
jobs:
guard:
runs-on: ubuntu-latest
steps:
- name: Repo guard
run: |
if [ "${{ github.repository }}" != "contoso/inventory-service" ]; then
echo "This workflow only runs in contoso/inventory-service"
exit 1
fi
Copying a repo-guarded workflow into another repository must produce an immediate failure — not a skipped job, not a warning. Templates do not prevent other-repo execution; the guard itself must fail the run. Validating only the repo name without the owner is insufficient, because forks under different owners retain the name. Actor conditions (if: github.actor == 'copilot-swe-agent') complement but do not replace the identity guard: an actor check restricts who can trigger, while the guard restricts where the workflow may execute. Both are needed for repo-scoped, agent-only workflows.
The guard must be the first step of the first job, before any secret is referenced.
Label Routing for Autonomy Staging
Labels stage autonomy — they do not confer it. A planning workflow reacts to issues: labeled with label agent-plan, needs issues: write to comment, and must not create branches or PRs. Labels do not grant merge rights. Branch protection status checks remain authoritative; an agent-execute label authorizes the agent to open a draft PR, but the PR still requires reviews and passing checks.
The issue-preparation order before assigning work to Copilot: (1) identify service scope, approval path, and operational risk → (2) define expected outputs and affected repository paths → (3) add success evidence such as plan output and required checks → (4) convert the completed request into an agent-ready GitHub Issue. Asking Copilot to infer forbidden actions from logs, or letting it apply infrastructure if tests pass, are distractors — risk classification is a human act performed before delegation.
Always record who approved readiness or triggered execution — traceability of the human decision is mandatory. github.event.sender.login or github.event.label.creator must appear in the workflow output or an audit comment.
Critical rules: Path → label mapping is fixed: docs/** → agent-low-risk; src/auth/**, infra/deploy/**, compliance/** → human-review-required. Low risk does not mean no validation. Repo guards must fail immediately on mismatch of owner/name. Required checks with paths: filters get stuck pending when the path does not match. Labels stage autonomy; they do not grant merge rights. Planning workflows need issues: write to comment. agent-ready triggers nothing if the workflow is workflow_dispatch-only.
Ready to practice?
Reading is step one — passing GH-600 means recognizing these patterns under exam pressure. Put the guide into practice:
- Browse every GH-600 question with verified answers → — free, with explanations and the exact YAML/CLI snippets you’ll see on the test.
- Start timed practice tests on ExamRoll.io → — the full question bank, weak-area targeting, in 20+ languages.
Pass the whole exam — not just this question
You found this answer. Get every verified question and explanation in one place, and save hours of prep. Free to start.
Pass your exam →