Skip to content
GitHub + Copilot End-to-End

Run the Full Cycle with Copilot CLI

In this tutorial, we will wire Sortie to GitHub Issues and the Copilot CLI, clone a repository, let the agent write and commit code, push the result to a branch, and move the issue to review. The entire stack is GitHub-native. No Jira, no Claude Code, no Anthropic API key.

The GitHub integration tutorial proved that Sortie can talk to your issue tracker. This tutorial adds three new pieces: a real agent (Copilot CLI), workspace hooks for git operations, and a prompt template that guides the agent through the task.

Prerequisites

  • GitHub integration tutorial completed: Sortie connects to your GitHub repository and SORTIE_GITHUB_TOKEN is set

  • Copilot CLI 1.0.51 or later installed on your machine. This adapter assigns its own session ID with --session-id, a flag an older CLI rejects; on an older CLI the run fails before it does any work, with the CLI’s own complaint about its command line rather than a message naming the version.

    copilot --version

    Confirm the version is 1.0.51 or later. If the command is not found, install the Copilot CLI and follow its own prerequisites.

  • GitHub authentication for Copilot CLI. The CLI resolves its own credential; which source it reads is the CLI’s own to document. Sortie confirms that credential actually works before it starts work on an issue, rather than only checking that a variable is set; see authentication: one token, two jobs. The fastest path is to reuse the token you already have:

    export GITHUB_TOKEN="$SORTIE_GITHUB_TOKEN"
  • A git repository on GitHub that you can push to, with SSH or HTTPS credentials configured:

    git ls-remote git@github.com:yourorg/yourrepo.git HEAD

    You should see a commit hash. If you get a permission error, fix your SSH or token setup before continuing.

No ANTHROPIC_API_KEY needed. That is the key difference from the Claude Code end-to-end tutorial: Copilot CLI authenticates through GitHub tokens, and a single token can serve both the tracker and the agent.

Create a GitHub issue

Create an issue with the backlog label. Pick a task that is concrete and verifiable. The agent reads the description as its primary instruction.

gh issue create --repo yourorg/yourrepo \
  --title "Create a health check endpoint" \
  --body "Add a /healthz endpoint that returns HTTP 200 with {\"status\": \"ok\"}. Create the handler file and a basic test." \
  --label backlog

Note the issue number in the output (e.g., #5). We will see it in the logs later.

Vague descriptions like “improve the API” produce vague results. Concrete tasks (add a file, fix a specific bug, write a test) work best with any coding agent.

Set up the project directory

Create a directory for this tutorial, separate from the GitHub integration work:

mkdir sortie-github-e2e && cd sortie-github-e2e

Write the workflow file

Create WORKFLOW.md with the full configuration. Replace yourorg/yourrepo with your actual repository:

WORKFLOW.md
---
tracker:
  kind: github
  api_key: $SORTIE_GITHUB_TOKEN
  project: yourorg/yourrepo
  active_states:
    - backlog
    - in-progress
  handoff_state: review
  terminal_states:
    - done

polling:
  interval_ms: 30000

workspace:
  root: ./workspaces

hooks:
  after_create: |
    git clone --depth 1 git@github.com:yourorg/yourrepo.git .
  before_run: |
    git fetch origin main
    git checkout -B "sortie/${SORTIE_ISSUE_IDENTIFIER}" origin/main
  after_run: |
    git add -A
    git diff --cached --quiet || \
      git commit -m "sortie(${SORTIE_ISSUE_IDENTIFIER}): automated changes"
    git push origin "sortie/${SORTIE_ISSUE_IDENTIFIER}" --force-with-lease
  timeout_ms: 120000

agent:
  kind: copilot-cli
  command: copilot
  max_turns: 3
  turn_timeout_ms: 1800000
  max_concurrent_agents: 1

copilot-cli:
  model: gpt-4.1
  max_autopilot_continues: 50
---

You are a senior engineer working in this repository.

## Task

**#{{ .issue.identifier }}**: {{ .issue.title }}
{{ if .issue.description }}

### Description

{{ .issue.description }}
{{ end }}
{{ if .issue.url }}

**Ticket:** {{ .issue.url }}
{{ end }}

## Rules

1. Read existing code before writing anything new.
2. Keep changes minimal. Implement exactly what the task requires.
3. Run any available lint and test commands before finishing.
{{ if not .run.is_continuation }}

## First run

Start by understanding the codebase structure. Check for existing patterns
(routing setup, test conventions) and follow them. Write the implementation,
add a test, and verify everything passes.
{{ end }}
{{ if .run.is_continuation }}

## Continuation (turn {{ .run.turn_number }}/{{ .run.max_turns }})

You are resuming. Run `git status` and check test output to understand the
current state. Continue from where the previous turn left off.
{{ end }}
{{ if and .attempt (not .run.is_continuation) }}

## Retry (attempt {{ .attempt }})

A previous attempt failed. Review workspace state and error output before
making changes. Do not repeat the same approach that failed.
{{ end }}

If you followed the Claude Code end-to-end tutorial, this file will look familiar. The hooks and prompt template are nearly identical. The differences are in the tracker and agent configuration.

Tracker: GitHub instead of Jira

tracker.kind: github uses the GitHub adapter. The project field takes owner/repo format, and api_key: $SORTIE_GITHUB_TOKEN is a single Bearer token, with no email:token format like Jira. State is managed through labels: when Sortie transitions an issue, it removes the old state label, adds the new one, and closes the issue if the target state is terminal. No Jira workflow configuration required.

Agent: Copilot CLI instead of Claude Code

agent.kind: copilot-cli uses the Copilot CLI adapter. Where the Claude Code tutorial sets permission_mode: bypassPermissions, Copilot CLI always runs with --autopilot and --no-ask-user, and adds --allow-all too as long as you leave allowed_tools unset. No extra permission field is needed.

The copilot-cli section is a pass-through to the Copilot CLI binary. max_autopilot_continues: 50 is the inner turn budget, analogous to claude-code.max_turns. With three Sortie turns and 50 autopilot continues each, the agent gets up to 150 total steps to finish the task. model: gpt-4.1 selects the LLM model. Replace it with your preferred model.

Authentication: one token, two jobs

SORTIE_GITHUB_TOKEN authenticates Sortie to the GitHub API. GITHUB_TOKEN (or GH_TOKEN, or COPILOT_GITHUB_TOKEN) authenticates Copilot CLI to GitHub’s AI backend. They can be the same token. If you ran the export GITHUB_TOKEN="$SORTIE_GITHUB_TOKEN" command from the prerequisites, both are already set.

Before it starts work on an issue, Sortie opens a short-lived session of its own and sends one request through Copilot CLI to confirm the credential actually answers, rather than only checking that a variable is set. A missing or rejected credential stops the run immediately with Copilot CLI’s own message about it, such as No authentication information found, instead of failing partway through the first turn. See credential verification for the mechanism every agent kind shares.

Workspace and hooks

The hooks work the same way as in the Claude Code tutorial: after_create clones the repo, before_run creates a branch from origin/main, and after_run commits and pushes. For a detailed walkthrough of the hook lifecycle and environment variables, see the hooks section in the Claude Code tutorial.

Prompt template

The template body is a Go text/template rendered per issue. It branches on three conditions: first run, continuation, and retry. The #{{ .issue.identifier }} prefix uses the # convention because GitHub Issues are referenced as #5, not PROJ-55.

Validate the configuration

Check for syntax errors before running:

sortie validate ./WORKFLOW.md

No output means no errors. Confirm with:

echo $?

This should print 0.

Run Sortie

Start Sortie:

sortie ./WORKFLOW.md

You should see output similar to this (timestamps and IDs will differ, and the tick completed lines carry more fields than shown here):

level=INFO msg="sortie starting" version=0.x.x workflow_path=/home/you/sortie-github-e2e/WORKFLOW.md
level=INFO msg="database path resolved" db_path=/home/you/sortie-github-e2e/.sortie.db
level=INFO msg="http server listening" addr=127.0.0.1:7678
level=INFO msg="sortie started"
level=INFO msg="tick completed" candidates=1 dispatched=1 ... running=1 retrying=0 ...
level=INFO msg="running hook" issue_id=5 issue_identifier=5 hook=after_create workspace=…/workspaces/5
level=INFO msg="running hook" issue_id=5 issue_identifier=5 hook=before_run workspace=…/workspaces/5
level=INFO msg="workspace prepared" issue_id=5 issue_identifier=5 workspace=…/workspaces/5
level=INFO msg="agent credential verified" issue_id=5 issue_identifier=5 duration_ms=…
level=INFO msg="agent session started" issue_id=5 issue_identifier=5 session_id=…
level=INFO msg="turn started" issue_id=5 issue_identifier=5 turn_number=1 max_turns=3

The agent credential verified line is Sortie proving your GitHub token works, in a short-lived session of its own, before it starts the working session below it. The agent is now working. A Copilot CLI session typically takes 3–10 minutes depending on the task complexity and model. The agent reads files, writes code, and runs tests. Each action appears as events in the log at debug level.

Notice that issue identifiers are bare numbers (5, not #5 or PROJ-55). Both Issue.ID and Issue.Identifier are the issue number for the GitHub adapter.

When the agent finishes, you will see:

level=INFO msg="turn completed" issue_id=5 issue_identifier=5 turn_number=1 max_turns=3
level=INFO msg="running hook" issue_id=5 issue_identifier=5 hook=after_run workspace=…/workspaces/5
level=INFO msg="worker exiting" issue_id=5 issue_identifier=5 exit_kind=normal turns_completed=1
level=INFO msg="handoff transition succeeded, releasing claim" issue_id=5 issue_identifier=5 handoff_state=review
level=INFO msg="tick completed" candidates=0 dispatched=0 ... running=0 retrying=0 ...

Here is the full lifecycle, step by step:

  1. Sortie polled GitHub and found issue #5 with a backlog label.
  2. after_create cloned the repository into workspaces/5/.
  3. before_run created the branch sortie/5 from origin/main.
  4. Sortie verified the credential in a session of its own, then Copilot CLI started the working session and worked on the task.
  5. The agent completed the turn and exited.
  6. after_run committed the changes and pushed the branch.
  7. Sortie removed the backlog label and added review, leaving the issue open for a human.
  8. The next poll found zero candidates and went idle.

Press Ctrl+C to stop Sortie.

Verify the results

Three things should be visible now: the code in the workspace, the branch on the remote, and the issue state in GitHub.

Check the workspace

Look at the git log in the workspace directory:

cd workspaces/5
git log --oneline -5

You should see the agent’s commit at the top:

a1b2c3d sortie(5): automated changes
f4e5d6c (origin/main) Initial commit

Check what the agent produced:

git diff HEAD~1 --stat

This shows the files the agent created or modified.

Check the remote branch

Back in any directory, verify the branch exists on your remote:

git ls-remote git@github.com:yourorg/yourrepo.git "refs/heads/sortie/5"

You should see a commit hash. The sortie/5 branch is on GitHub, ready for a pull request.

Check GitHub

Open the issue in the browser, or check from the command line:

gh issue view 5 --repo yourorg/yourrepo

Verify three things: the issue is still open, the backlog label is gone, and the review label is present. Handoff parks the issue for a human rather than finishing it, so closing it is a call you make after reading the branch.

If the label did not change: review the Sortie logs for error messages and confirm your token has repo scope.

Check the dashboard

Open http://127.0.0.1:7678/ in a browser while Sortie is running. You will see summary cards (running sessions, retry queue, free slots, total tokens) and a run history table showing the completed session with its issue identifier, turn count, duration, and exit status.

What we built

We ran the complete Sortie lifecycle with Copilot CLI on GitHub Issues, entirely GitHub-native. One token authenticates both the tracker and the agent. Sortie polled GitHub, cloned the repository, launched the Copilot CLI, let it write and test code, pushed the result to a branch, and moved the issue to review.

The same orchestration loop powers the Claude Code end-to-end tutorial and the Codex end-to-end tutorial with different agents and trackers. Sortie’s adapter-agnostic design means swapping copilot-cli for claude-code or codex is a config change. The prompt template, hooks, and overall flow carry over.

Where to go next:

Was this page helpful?