Skip to content

Git and GitHub

Thulu works on GitHub repos. Each task gets its own branch, task-<id>, in every repo it touches, and ends in one pull request per repo. The agents make the changes; Thulu publishes the branch to GitHub with the project’s credential, which never enters the agent’s container, and shows you the state of each PR on the task. By default no agent merges or triggers deploys: an admin decides whether to grant that to an agent, tool by tool.

It works with GitHub and GitHub Enterprise Server. If the code is not on GitHub, the project can be an uploaded folder, with no provider and no pull requests (see Projects).

A project authenticates in one of three ways. You choose one when you create it, under “Repository access”:

Option What it does When to use it
“GitHub App” Thulu asks GitHub for a short-lived token (about an hour) for each operation, limited to the project’s repo and to the permissions that operation needs. It stores nothing long-lived. Whenever you can. On the SaaS the App is already registered and you only have to install it on your organization.
“Saved credential” Uses a GitHub personal token stored under Credentials. It is validated when you save it and you can rotate or disable it from there. Self-hosted without an App, or a repo on an account where you cannot install the App.
“Public repo” Clones without credentials. To read a public repo. Without a credential, Thulu cannot push or open PRs.

If a project is linked to the App, the App takes precedence: the project’s credential selector is disabled and says “The App installation above is what clones and pushes. Unlink it to use a credential instead.”

Section titled “Recommended flow: install the App and create the project”

On the SaaS, or on a self-hosted install where the operator has already connected the App, connecting a repo goes like this. You need the admin role in the workspace to create projects and install the App, and permission on GitHub to install Apps on the organization (if you do not have it, GitHub lets you request it from an admin).

  1. Open “Projects” in the sidebar and click “New project” (or press N).

  2. Under “Repository access”, the “GitHub App” option is already selected. If it does not appear, the App is not connected on this installation: see the self-hosted section below or use a token. If the App is not yet installed on your organization, click “Connect GitHub” (with the App already installed on some organization, the button says “Install on another organization”). Thulu takes you to GitHub, where you choose the organization, which repos to enable (all or some) and confirm the installation.

  3. GitHub sends you back to Projects with the notice “GitHub connected — pick a repository” and the modal open again, with what you had already typed. Pick the organization and the repository: the name, the URL and the default branch fill in by themselves.

    New project modal with GitHub App selected, the organization acme and the repo acme/backoffice selected; name, URL and default branch filled in
    When you pick acme/backoffice, the modal fills in the name, the URL and the default branch (develop) that GitHub reports.
  4. Write a short description (the agent reads it at init), check the default board and click “Create”. The modal closes and Thulu clones the repo in the background with a read-only token; it stores only the link to the installation. The project card says “Cloning” until it finishes.

From then on, every task that links this project works on its own branch (see What happens to a task). For an agent to do the work and open the PR, the column has to run an agent that has argus-github; that is set up in Boards and stages and Agents.

If the connection fails, the notice says why: “the install link expired, was already used, or was opened in another browser”, “that installation is already connected to another workspace”, “your GitHub user can’t see that installation”, among others. If your organization requires an admin to approve Apps, you will see “Install requested — waiting for your GitHub org admin to approve” and the repo only appears once they approve it.

If you created the project with a token or as a public repo and installed the App afterward, open the project, “Repository” tab, “GitHub access” panel. Pick the installation under “GitHub App installation” and click “Link”: Thulu checks that the installation reaches the repository before saving. From then on the App takes precedence and the credential selector is disabled. “Unlink” goes back to the saved credential or to public access; the App stays installed on GitHub.

Connect the GitHub App on a self-hosted install

Section titled “Connect the GitHub App on a self-hosted install”

On self-hosted, the operator registers an App of their own on GitHub and gives its details to Thulu. The operator is a workspace owner named by OCTOPUS_OPERATOR_WORKSPACE; thulu init points that variable at the first workspace it creates. A single App serves all the workspaces of the installation. On the SaaS this step is already done: the panel is read-only and the only thing left active is “Install on an organization”.

Under “Settings” → “Credentials”, the “GitHub App” panel offers “Create the App on GitHub”. GitHub opens its form with the permissions, the webhook and this installation’s addresses already filled in: you choose the “Organization” (empty, the App lands on your personal account), check whether agents will dispatch workflows or approve deployments (adds Actions and Deployments write), confirm the name on GitHub and come back with the App connected. The name it proposes is “Thulu” followed by the installation’s host.

GitHub has to be able to reach the installation’s address to send webhooks. If you opened Thulu from a local or private address, the modal warns you: open it from its public address before creating the App.

  1. On GitHub, register an App on your organization (Settings → Developer settings → GitHub Apps → New GitHub App).

    Required repository permissions: Contents and Pull requests read and write, and Actions read. Every token Thulu requests to push, open PRs or read their state includes Actions read; without that permission GitHub rejects the token. Optional: Checks read (to see all the CI on the task), Actions write if agents will dispatch, retry or cancel workflows, and Deployments write only if you are going to grant deployment_approve.

    As the Setup URL use https://<your-thulu>/github/setup and check “Redirect on update”. If you prefer that GitHub identify whoever installs, turn on “Request user authorization (OAuth) during installation”: GitHub stops using the Setup URL and goes back to the Callback URL, https://<your-thulu>/github/oauth/callback, and in Thulu you will fill in the Client ID and Client secret. For the webhook, use https://<your-thulu>/github/webhook with a secret, subscribed to the events you are going to use (pull request, reviews and comments, push, check suite, deployment status). If the App will be installed on another organization, choose “Any account” for where it can be installed.

    At the end, generate a private key: GitHub downloads a .pem file.

  2. In Thulu, go to “Settings” → “Credentials”. The “GitHub App” panel says “Not connected”. Click “Connect an existing App”.

    GitHub App panel in the Not connected state, with the explanatory text and the buttons Create the App on GitHub and Connect an existing App
    Without an App, each project uses a personal token from the credentials list.
  3. Fill in “App ID” (it is in the App’s settings, under About) and paste the full .pem into “Private key”. If you turned on OAuth or the webhook, open “OAuth and webhook” and fill in “Client ID” and “Client secret” (they are on the App’s page on GitHub) or the “Webhook secret” you chose. Click “Check and connect”: Thulu validates against GitHub that the key matches that App ID before saving. If the App is missing a required permission, the panel names it (“The App lacks permissions Thulu needs…”); if it lacks Checks read, it warns that the pull requests section will only see GitHub Actions checks.

    Connect a GitHub App modal with the fields App ID, Private key and the OAuth and webhook section open with Client ID, Client secret and Webhook secret
    The text at the top of the modal asks for Contents and Pull requests write and Actions read. OAuth and webhook are optional: the webhook feeds automations and Evidence with GitHub events and refreshes the PR section instantly.
  4. With the App connected, click “Install on an organization”. GitHub sends you back to Projects with the “New project” modal open, as in the recommended flow. Each installation appears in the panel’s table with its organization, whether it covers all repos or only selected ones, and its status.

    GitHub App panel in the Connected state: the App thulu-example-store with its App ID and Client ID, the buttons Remove and Install on another organization, and the active installation example-store with selected repositories and the Unlink button
    “Unlink” cuts that installation's tokens immediately; the App stays installed on GitHub.

If the App is configured through environment variables (OCTOPUS_GITHUB_APP_ID, OCTOPUS_GITHUB_APP_PRIVATE_KEY and the others), the panel says so and does not let you change it from the UI. For GitHub Enterprise Server, the operator points OCTOPUS_GITHUB_URL and OCTOPUS_GITHUB_API_URL at their server. Deployment details are in Self-hosted operations.

  1. Generate the token on GitHub. A classic token needs the repo scope, plus workflow if agents will dispatch Actions.

  2. In “New project”, choose “Saved credential” and click “New credential…”. Enter a name (for example “GitHub — deploys”), paste the token into “Personal access token” and click “Save credential”. Thulu validates it against api.github.com and stores it under Credentials; if GitHub rejects it, it is saved anyway with the notice that it will not be able to clone.

  3. Fill in “Name”, “Repository URL” and, if you want one other than the repo’s own, “Default branch”, and click “Create”.

To change the credential of a project that already exists, open it, go to the “Repository” tab and pick another under “GitHub access”. The “Manage” button takes you to Credentials, where they are created, rotated and shared across projects. That walkthrough is in Credentials and engines.

Repository tab of the project web-store with the GitHub access panel: credential selector on none, buttons Manage and Save, and the notice that there are no GitHub credentials in the workspace
Without a credential or an App, the repo has to be public to clone it and Thulu cannot push.

What happens to a task: branch, worktree and push

Section titled “What happens to a task: branch, worktree and push”

You do not create branches by hand. This is what Thulu does with each task that has linked projects:

  • When the task leaves its first column, Thulu fetches the latest version of the default branch from GitHub and creates the task-<id> branch with a worktree (a working copy of its own) in each linked project. If it could not fetch it, it leaves a comment on the task with the commit it started from, which may be behind.
  • A task with two repos has two worktrees and ends in two PRs, one per repo, on the same task-<id> branch.
  • Subtasks work on the parent task’s branch and go into its pull request. On the subtask, “Branch and PR” says “Shared with the parent”.
  • “Fork task” (the “…” menu on the task) creates a new worktree from the original’s branch. “Clone task” copies only the title, description, repos and assignments, with no branch.
  • A column with “Push the branch” publishes the branch to GitHub when the run ends well. On the factory board, “In progress” has it turned on; it runs the agent you assign to the task (the workspace starts with no agents).

How commits reach GitHub depends on where the agents run. You do not have to configure it, but it explains some comments you will see on the task:

Since 0.3.3, the agent works in its own copy of the repo and uses git like any person: git commit, git fetch origin && git rebase origin/main and git push. The push reaches Thulu, which moves the task’s branch to what the agent sent and publishes it to GitHub before giving control back, so the agent can then open the PR. git fetch brings what GitHub has on main (Thulu refreshes it at most every 20 seconds per project).

If main moved ahead, the agent rebases and resolves conflicts in its copy, and Thulu publishes the new history with --force-with-lease. If the task’s branch has a commit that the agent’s copy does not carry, Thulu does not overwrite it: it does not publish that project and leaves a comment that starts with “The agent’s commits for project … could not be brought onto the task branch…”. The GitHub token never enters the agent’s pod or the sandbox.

A commit that touches .github/workflows/ cannot be pushed with the App unless the operator turns on OCTOPUS_GITHUB_ALLOW_WORKFLOW_WRITES=1 and the App has the Workflows write permission: a pushed workflow runs on the runners with the repo’s secrets. Without that variable, GitHub rejects the push and the error names it.

The agent opens the PR with its pr_create tool and the task records it right away (“Agent opened PR: …”).

Activity section of the task Apply the WELCOME10 coupon: run 1 of developer finished in 51 minutes and an agent comment detailing PR #88 of payments-api and #214 of web-store, with the files and tests it touched
When the run ends, developer reports which PR it opened in each repo, what changed and that it merged nothing.

There are two other ways for it to open:

  • A column with “Open a pull request” opens the PR of each repo that does not have one yet when a task enters it. The factory board has no column with that option. To turn it on, on the board click “Edit board”, open the column’s ⋯ menu, choose “Behavior” and check “Open a pull request”; it saves when you click it. The details are in Boards and stages.
  • “Force PR” does the same by hand, in any stage.
Behavior modal of In progress with Default agent, Move on when the agent finishes and Push the branch checked, and Open a pull request unchecked
“Push the branch” publishes when the run ends; “Open a pull request” opens the PR when the task enters the column.

Open the task. In the properties column, “Branch and PR” shows the branch (with a button to copy the name). PRs have their own section, “Pull requests”, at the full width of the task below the description, with one row per PR. On a subtask the section shows the parent task’s PRs. The row summarizes, from left to right: number, title, CI as passed/total (with an icon that spins while it runs), the review state (eye: review pending; check: approved; alert: changes requested) and “Ready” when it is approved, CI passed and GitHub can merge it. The left border takes the color of the most urgent item: red if CI failed, there are conflicts or changes were requested.

Pull requests section of the task Pay with gift card at checkout, with two repos: #214 with CI 5/8 running and review pending, and #88 with CI 6/6, approved and Ready
A task with two repos has two PRs on the same branch.

Clicking a row expands it with this data:

Item What it shows
Project The Thulu project the PR belongs to.
Files, Lines, Commits Number of changed files, lines added and removed, and commits.
Branch task-<id> → base branch.
CI How many checks passed, failed and are still running, out of the total on the last commit.
Review “No review requested”, “Review pending”, “Approved” or “Changes requested”.
Merge “Can be merged”, “Blocked by the branch’s rules”, “Behind the base branch”, “Conflicts with the base branch” or “GitHub has not worked it out yet”.
Updated When it last changed on GitHub.
Pull requests section expanded with PR #214 of web-store: red border, CI 7 of 8 with 1 failed, review pending and merge blocked by the branch's rules; below it #88 of billing-api with CI 6 of 6, approved and Ready
#214 has the red border because 1 of its 8 checks failed; GitHub blocks it until it passes and someone approves it. #88 has green CI and the approval, and says Ready.

Below is “Open on GitHub”. Thulu reads this data with the project’s credential, keeps it for 30 seconds and requests it again every 20 seconds while something is moving (CI running or review pending) and every minute otherwise. With the App’s webhook active, a GitHub event refreshes it sooner.

If the project has no credential, the row keeps the link to the PR and says “No GitHub credential for this project.” with a link to add one. If the App does not have the Checks: read permission, CI counts only GitHub Actions jobs and the row says so.

When the task has a branch and no PR, “Branch and PR” shows the “Force PR” button. It is also in the panel’s “…” menu, which you open with the period key; with that menu open, P runs it. Thulu pushes the branch if needed and opens a PR in each linked repo that does not have one, with the task’s title and, in the body, the description, the agents’ notes and a line saying Thulu opened it from Force PR. If the agent had already opened one, it links it to the task.

Panel of the task Search with suggestions as you type with the Force PR button below the branch and a notice: Force PR: project web-store has no GitHub credential. Pick one in Project → Repository → GitHub access, with the link Open the project
Without a credential, Force PR opens nothing and the notice says where to pick one.

A project uploaded from a folder does not go through GitHub. If all of the task’s projects are uploaded, “Force PR” does not appear and the “Branch and PR” row offers “Merge into…”, which takes the task’s branch to the branch you choose. It is explained in Projects.

Agents talk to GitHub through the argus-github MCP. An admin wires it per stage in the agent’s editor (“MCP” tab); the details of that screen are in MCPs and Agents. The token is resolved on the server side on every call: the agent never sees it.

Granted with What it includes
argus-github@read Read PRs, their diff, files, comments, review threads, reviewers and CI; list labels; see the branch’s state; list Actions workflows and runs, their jobs, logs and artifacts.
argus-github@write Everything in read, plus committing and pushing the task’s branch, opening PRs, commenting, reviewing, replying to and resolving threads, managing labels, reviewers and assignees, editing the PR, and retrying or canceling Actions runs. It is what an agent that implements needs.
By name only pr_merge (merge a PR), run_dispatch (dispatch a workflow) and deployment_approve (approve a deployment waiting for review). No scope includes them, not even write.

By default no agent merges or deploys: an admin decides whether to grant pr_merge or the deploy tools, and checks them one by one. If argus-github does not appear on the agent’s “MCP” tab, it is not enabled in the workspace: see MCPs.

  1. Open “Agents”, pick the agent and go to the “MCP” tab.

  2. In the stage where you want it to merge, with argus-github turned on, click argus-github’s tools button. The list of 42 tools for that stage opens, grouped into Read, Write and “Deploy · granted by name”.

  3. In the Deploy group, check pr_merge and click “Done”. Then “Save” in the agent’s editor. The stage ends up with argus-github@write+pr_merge: everything in write plus that tool.

    Side panel of argus-github's tools on the In progress stage, 39 of 42 chosen, with the group Deploy · granted by name and run_dispatch, deployment_approve and pr_merge unchecked
    With argus-github checked on the stage, 39 of 42 are chosen: no scope covers the ones in the Deploy group and you check them one by one.

The checkbox at the top of the panel says “All, except the ones granted by name”: it checks everything a scope covers and leaves out pr_merge, run_dispatch and deployment_approve.

In a task run, the agent merges with the task’s token. In a chat with “Act for me”, pr_merge does not run immediately: it waits for the person to confirm it in Thulu, and the daemon runs it with a token for that single call (see Chat).

Deploy through GitHub Actions from an agent

Section titled “Deploy through GitHub Actions from an agent”

An agent can dispatch a workflow, follow it until it finishes and read the logs if it fails. You need:

  1. A workflow in the repo that accepts manual dispatch (on: workflow_dispatch, with its inputs if it has any). This is done in the repo, outside Thulu.

  2. Permission for Actions: with the App, Actions write; with a classic token, the workflow scope. Thulu only requests actions:write for a run whose stage grants run_dispatch, run_rerun or run_cancel.

  3. A stage (for example a “Deploy” column on your board) where the agent has run_dispatch by name, in addition to the Actions read tools. On the “MCP” tab of the agent’s editor, “Deploy template” grants on that stage argus-github@read plus run_dispatch, run_watch and run_logs. By hand: argus-github@read+run_dispatch, or argus-github@write+run_dispatch if it also has to retry.

  4. In that stage’s prompt, which workflow to dispatch and with which inputs. The agent uses workflow_view to read the inputs, run_dispatch to dispatch it, run_watch to wait for the result and run_jobs with run_logs to diagnose a failure.

With the App’s webhook active, Evidence environments can listen to these deploys (the deployment status event) and run the recorded cases after a successful one; see Evidence. And GitHub events (PR comment, review comment, review, PR, push, check suite) can start a recipe in Automations.

What you see What is happening and how to get out
“No GitHub credential for this project.” on the PR row The project has no App or credential that can read the repo. Link one under the project → “Repository” → “GitHub access”. If the project is linked to the App, check whether the “GitHub App” panel under Credentials warns about a missing permission (Actions read is required): add it on GitHub and accept the new permission on the installation.
Comment “Could not fetch the latest default branch from GitHub…” The initial fetch failed (expired credential, suspended App). The branch started from a possibly old commit. Check the connection and, if needed, fork the task once it is fixed.
Comment “Auto-push of branch failed for project…” GitHub rejected the push. If the error names OCTOPUS_GITHUB_ALLOW_WORKFLOW_WRITES, the agent touched a workflow. If not, check the credential; the agent or you can retry with “Force PR”.
Comment “The agent’s commits for project … could not be brought onto the task branch…” (Kubernetes) The task’s branch has a commit that the agent’s copy does not have, for example because someone pushed by hand or the agent rewrote history with a squash. Thulu does not publish that project until the agent brings in that commit.
The row says “Only GitHub Actions: add Checks: read to the GitHub App…” Accept the new permission in the installation’s settings on GitHub.
“GitHub connection failed (that installation is already connected to another workspace…)” An installation belongs to a single workspace. Ask the operator to move it.