GitHub access
A step gets GitHub authority from a github block:
steps:
- id: implement
environment: development
max_runtime: 90m
idle_timeout: 10m
github:
connection: example-github
repositories:
- example/project
permissions:
contents: write
pull_requests: write
agent:
provider: codex
mode: full-access
prompt:
- text: |
Implement the request, commit, push the branch, and open a
pull request with `gh pr create`.
Call hub.finish_execution when the step is complete.
- text: |
<user-prompt>
${{ paseo.prompt }}
</user-prompt>
The agent can clone, commit, push, and call gh in the listed repositories. Nothing is configured on the daemon host.
Nothing else grants GitHub authority. A run triggered from a GitHub event has no token and no git configuration unless its step declares the block.
The block
| Field | Notes |
|---|---|
connection | The project's GitHub connection slug. |
repositories | The repositories the token can reach. On a GitHub-triggered run, this defaults to the triggering repository. Required for other triggers. |
permissions | GitHub installation-token permission names, such as contents, pull_requests, issues. Defaults to contents: read. |
duration | How long the token lives. Defaults to 1h, the GitHub maximum. Shorter revokes early; longer is invalid. |
Permissions cannot exceed what the GitHub App installation was granted. A block asking for contents: write on an installation that only has read fails.
Hub mints the token when the step starts and revokes it when the execution ends, whatever the outcome.
What the agent's environment contains
Hub supplies GH_TOKEN and git configuration through environment variables alone:
- Commits author as the App's bot account, with its GitHub noreply email.
git@github.comremote URLs are rewritten to HTTPS, and the credential helper answers with the token.- Global and system git config are ignored, and nothing prompts a terminal.
The daemon host's own git identity and credentials are never used or changed.
Only the step that needs it
Authority is per step, so a routed workflow grants it only to the branch that does the work. A Slack-triggered run:
environments:
- name: triage
kind: daemon
daemon: my-macbook
cwd: /Users/you/code/project
- name: development
kind: daemon
daemon: my-macbook
cwd: /Users/you/code/project
worktree:
mode: branch-off
newBranch: hub/work
base: origin/main
triggers:
- name: slack-work
on: slack.mention
max_runtime: 3h
filters:
workspace: T01234567
channels: [C01234567]
from_users: [U01234567]
steps:
- id: classify
environment: triage
max_runtime: 2m
idle_timeout: 30s
agent:
provider: codex
options:
approval_policy: never
sandbox_mode: read-only
web_search: disabled
prompt:
- text: |
Decide whether this request needs an answer or a change.
Call hub.finish_execution with the decision as the structured result.
- text: |
<user-prompt>
${{ paseo.prompt }}
</user-prompt>
output:
schema:
type: object
additionalProperties: false
required: [kind]
properties:
kind:
enum: [answer, change]
- id: answer
if: ${{ steps.classify.outputs.kind == 'answer' }}
environment: triage
max_runtime: 10m
idle_timeout: 2m
agent:
provider: codex
options:
approval_policy: never
sandbox_mode: read-only
web_search: disabled
prompt:
- text: |
Answer the request without changing files.
Call hub.reply to send your answer to the originating conversation.
Call hub.finish_execution when the step is complete.
- text: |
<user-prompt>
${{ paseo.prompt }}
</user-prompt>
allow_outputs:
- type: slack.reply
- id: change
if: ${{ steps.classify.outputs.kind == 'change' }}
environment: development
max_runtime: 90m
idle_timeout: 10m
github:
connection: example-github
repositories:
- example/project
permissions:
contents: write
pull_requests: write
agent:
provider: codex
mode: full-access
prompt:
- text: |
Make the change, verify it, commit, push the branch, and open a
pull request with `gh pr create`.
Call hub.reply with a link to the pull request.
Call hub.finish_execution when the step is complete.
- text: |
<user-prompt>
${{ paseo.prompt }}
</user-prompt>
allow_outputs:
- type: slack.reply
The classifier and the answer step read untrusted text with no GitHub authority. Only the selected worker holds a token, scoped to one repository. Hub security covers the rest of the authority model.
Other integrations
Any connection value can be supplied explicitly through a step's environment:
steps:
- id: use-connection
env:
SOME_TOKEN: "${{ paseo.connections.some-connection.token }}"
Hub resolves the value when it launches the step and does not persist it. The github block adds repository and permission restrictions plus the git setup needed for GitHub work.