Skip to main content
The GitHub provider fetches files, directory listings, issues, and pull requests from GitHub repositories. It uses the GitHub API for all operations and supports automatic staleness detection.

Quick Start

The GitHub provider works without configuration. Pass the repository as the first argument:
{{ colin.github.file("PrefectHQ/prefect", "README.md").content }}
{{ colin.github.issue("PrefectHQ/prefect", 123).title }}
{{ colin.github.pr("https://github.com/PrefectHQ/prefect/pull/456").state }}
For frequently accessed repositories, configure named instances to avoid repeating the repo:
{{ colin.github.prefect.file("README.md").content }}
{{ colin.github.prefect.issue(123).title }}

Configuration

Named instances are optional but useful for repositories you access frequently or that require authentication:
colin.toml
[[providers.github]]
name = "prefect"
repo = "PrefectHQ/prefect"
token = "${GITHUB_TOKEN}"
FieldRequiredDescription
nameYesProvider instance name for template access
repoYesRepository in owner/repo format
tokenNoGitHub token for private repos and higher rate limits
Each configured repository becomes accessible as colin.github.{name} in templates.

Multiple Repositories

Configure multiple repositories as separate provider instances:
colin.toml
[[providers.github]]
name = "prefect"
repo = "PrefectHQ/prefect"

[[providers.github]]
name = "docs"
repo = "PrefectHQ/prefect-docs"
token = "${GITHUB_TOKEN}"
Access each by name:
{{ colin.github.prefect.file("README.md") }}
{{ colin.github.docs.file("index.md") }}

Authentication

Without a token, requests are subject to GitHub’s anonymous rate limit (60 requests per hour). With a token, the limit increases to 5,000 requests per hour. For private repositories, a token with appropriate permissions is required.

Template Functions

FunctionPurpose
file()Fetch a file’s content
ls()List directory contents
issue()Fetch a single issue
pr()Fetch a single pull request
issues()List issues with filters
prs()List pull requests with filters

file()

Fetch a file from a repository. Without configuration, pass the repo as the first argument:
{# Default provider: repo as first argument #}
{{ colin.github.file("PrefectHQ/prefect", "README.md").content }}
{{ colin.github.file("owner/repo", "src/main.py", ref="v1.0").content }}

{# Named instance: repo pre-configured #}
{{ colin.github.prefect.file("README.md").content }}
{{ colin.github.prefect.file("src/prefect/flows.py", ref="main").content }}
Default provider arguments:
ArgumentRequiredDefaultDescription
repoYesRepository in owner/repo format
pathYesFile path in the repository
refNo"HEAD"Git ref (branch, tag, or commit SHA)
watchNoTrueTrack for staleness detection
Named instance arguments (repo already configured):
ArgumentRequiredDefaultDescription
pathYesFile path in the repository
refNo"HEAD"Git ref (branch, tag, or commit SHA)
watchNoTrueTrack for staleness detection
Returns a GitHubFileResource with the file content.

ls()

List directory contents. Without configuration, pass the repo as the first argument:
{# Default provider: repo as first argument #}
{% for entry in colin.github.ls("PrefectHQ/prefect", "src/prefect/") %}
- {{ entry.name }} ({{ entry.type }})
{% endfor %}

{# Named instance: repo pre-configured #}
{% for entry in colin.github.prefect.ls("src/prefect/") %}
- {{ entry.name }} ({{ entry.type }})
{% endfor %}
Default provider arguments:
ArgumentRequiredDefaultDescription
repoYesRepository in owner/repo format
pathNo""Directory path (empty for root)
refNo"HEAD"Git ref (branch, tag, or commit SHA)
watchNoTrueTrack for staleness detection
Named instance arguments (repo already configured):
ArgumentRequiredDefaultDescription
pathNo""Directory path (empty for root)
refNo"HEAD"Git ref (branch, tag, or commit SHA)
watchNoTrueTrack for staleness detection
Returns a GitHubListingResource that can be iterated to get entries.

issue()

Fetch a single issue by number or URL:
{# By number with repo #}
{{ colin.github.issue("owner/repo", 123).title }}

{# By number with configured repo #}
{{ colin.github.prefect.issue(123).content }}

{# By full URL #}
{{ colin.github.issue("https://github.com/owner/repo/issues/123").state }}
Arguments:
ArgumentRequiredDefaultDescription
number_or_urlYesIssue number or full GitHub URL
repoNoRepository in owner/repo format (required if using number without configured repo)
watchNoTrueTrack for staleness detection
Returns a GitHubIssueResource with the issue content and metadata.

pr()

Fetch a single pull request by number or URL:
{# By number with repo #}
{{ colin.github.pr("owner/repo", 456).title }}

{# By number with configured repo #}
{{ colin.github.prefect.pr(456).head_ref }}

{# By full URL #}
{{ colin.github.pr("https://github.com/owner/repo/pull/456").state }}
Arguments:
ArgumentRequiredDefaultDescription
number_or_urlYesPR number or full GitHub URL
repoNoRepository in owner/repo format (required if using number without configured repo)
watchNoTrueTrack for staleness detection
Returns a GitHubPRResource with the PR content and metadata.

issues()

List issues with optional filters:
{# Open issues in configured repo #}
{% for issue in colin.github.prefect.issues() %}
- #{{ issue.number }}: {{ issue.title }}
{% endfor %}

{# Filtered issues #}
{% for issue in colin.github.issues("owner/repo", state="open", labels=["bug"]) %}
{{ issue.title }} ({{ issue.assignees | join(", ") or "unassigned" }})
{% endfor %}
Arguments:
ArgumentRequiredDefaultDescription
repoNoRepository in owner/repo format (uses configured repo if None)
stateNo"open"Filter by state: "open", "closed", or "all"
labelsNoNoneFilter by label names
assigneeNoNoneFilter by assignee username
limitNo30Maximum number of issues
watchNoTrueTrack for staleness detection
Returns a GitHubIssuesResource that can be iterated.

prs()

List pull requests with optional filters:
{# Open PRs in configured repo #}
{% for pr in colin.github.prefect.prs() %}
- #{{ pr.number }}: {{ pr.title }} ({{ pr.head_ref }} -> {{ pr.base_ref }})
{% endfor %}

{# PRs targeting main #}
{% for pr in colin.github.prs("owner/repo", state="open", base="main") %}
{{ pr.title }} - {{ pr.state }}
{% endfor %}
Arguments:
ArgumentRequiredDefaultDescription
repoNoRepository in owner/repo format (uses configured repo if None)
stateNo"open"Filter by state: "open", "closed", or "all"
baseNoNoneFilter by base branch
headNoNoneFilter by head branch
limitNo30Maximum number of PRs
watchNoTrueTrack for staleness detection
Returns a GitHubPRsResource that can be iterated.

Objects

GitHubFileResource

Returned by file():
PropertyTypeDescription
contentstrFile content
pathstrFile path in the repository
repostrRepository in owner/repo format
git_refstrOriginal git ref
resolved_shastrResolved commit SHA

GitHubListingResource

Returned by ls():
PropertyTypeDescription
contentstrNewline-separated list of paths
entrieslist[GitHubEntry]Directory entries
pathstrDirectory path
tree_shastrTree SHA for versioning
Supports iteration:
{% for entry in colin.github.prefect.ls("src/") %}
{{ entry.name }}
{% endfor %}

GitHubEntry

Each entry in a directory listing:
PropertyTypeDescription
pathstrFull path from repository root
namestrFilename only
typestr"file" or "dir"
shastrBlob or tree SHA
sizeint | NoneFile size in bytes (None for directories)

GitHubIssueResource

Returned by issue():
PropertyTypeDescription
contentstrIssue title and body as markdown
repostrRepository in owner/repo format
numberintIssue number
titlestrIssue title
bodystr | NoneIssue body (markdown)
statestr"open" or "closed"
labelslist[str]Label names
assigneeslist[str]Assignee usernames
authorstr | NoneUsername of creator
urlstrHTML URL to issue
updated_atdatetimeLast update time
created_atdatetimeCreation time
closed_atdatetime | NoneWhen closed (None if open)
comments_countintNumber of comments

GitHubPRResource

Returned by pr():
PropertyTypeDescription
contentstrPR title and body as markdown
repostrRepository in owner/repo format
numberintPR number
titlestrPR title
bodystr | NonePR body (markdown)
statestr"open", "closed", or "merged"
labelslist[str]Label names
assigneeslist[str]Assignee usernames
authorstr | NoneUsername of creator
urlstrHTML URL to PR
head_refstrSource branch name
base_refstrTarget branch name
head_shastrCurrent head commit SHA
is_draftboolWhether PR is a draft
updated_atdatetimeLast update time
created_atdatetimeCreation time
closed_atdatetime | NoneWhen closed
merged_atdatetime | NoneWhen merged
additionsintLines added
deletionsintLines deleted
changed_filesintFiles changed

GitHubIssuesResource

Returned by issues():
PropertyTypeDescription
contentstrNewline-separated issue summaries
issueslist[GitHubIssueResource]List of issue resources
Supports iteration and len():
{% for issue in colin.github.prefect.issues(labels=["bug"]) %}
#{{ issue.number }}: {{ issue.title }}
{% endfor %}

GitHubPRsResource

Returned by prs():
PropertyTypeDescription
contentstrNewline-separated PR summaries
prslist[GitHubPRResource]List of PR resources
Supports iteration and len():
{% for pr in colin.github.prefect.prs(state="open") %}
#{{ pr.number }}: {{ pr.title }}
{% endfor %}

Dependency Tracking

By default, all methods automatically track dependencies (watch=True). Documents recompile when tracked resources change:
{# Automatically tracked — recompiles when file changes #}
{{ colin.github.prefect.file("README.md").content }}

{# Automatically tracked — recompiles when issue is updated #}
{{ colin.github.prefect.issue(123).title }}

{# Opt out of tracking with watch=False #}
{{ colin.github.prefect.issue(123, watch=False).title }}
Issues and PRs are tracked by their updated_at timestamp. Any edit to the issue (title, body, labels, comments, etc.) triggers recompilation. Collections like issues() and prs() track all items collectively:
{# Recompiles when any issue in the list is updated #}
{% for issue in colin.github.prefect.issues(labels=["bug"]) %}
- #{{ issue.number }}: {{ issue.title }}
{% endfor %}

Staleness Detection

The GitHub provider uses different versioning strategies for different resource types: Files and directories resolve git refs to commit SHAs:
Ref TypeBehavior
Branch (main)Resolves to tip SHA; recompiles when branch advances
Tag (v1.0)Resolves to tagged commit SHA; stable unless tag is moved
SHA (abc123)Immutable; never triggers recompilation
Issues and PRs use their updated_at timestamp. Any modification (edits, comments, label changes, state changes) triggers recompilation. Collections (issues(), prs()) compute a hash of all item numbers and their update times. Changes to any item in the list, or items entering/leaving the list, trigger recompilation.

Error Handling

StatusExceptionDescription
404FileNotFoundErrorRef, path, or file not found
401PermissionErrorAuthentication required (private repo)
403PermissionErrorRate limited or access denied

Examples

Include External Documentation

Pull documentation from another repository:
models/external-docs.md
---
name: External Docs
---

# API Reference

{{ colin.github.prefect.file("docs/api/flows.md", ref="main").content }}

Generate Index from Directory

Create an index of example files:
models/examples-index.md
---
name: Examples Index
---

# Examples

{% for entry in colin.github.prefect.ls("examples/") %}
{% if entry.type == "file" and entry.name.endswith(".py") %}
## {{ entry.name }}

```python
{{ colin.github.prefect.file(entry.path).content }}
```

{% endif %}
{% endfor %}

Pin to a Specific Version

Reference files from a tagged release:
models/v3-config.md
---
name: V3 Configuration
---

Configuration for Prefect v3.0.0:

```python
{{ colin.github.prefect.file("src/prefect/settings.py", ref="v3.0.0").content }}

### Quick Access Without Configuration

Fetch files from any repository without pre-configuring it:

```jinja models/any-repo.md
---
name: External Reference
---

# License from another repo

{{ colin.github.file("anthropics/claude-code", "LICENSE").content }}

Track an Issue

Create a document that updates when an issue changes:
models/tracked-issue.md
---
name: Feature Request Status
---

{% set issue = colin.github.prefect.issue(123) %}
# {{ issue.title }}

**Status:** {{ issue.state }}
**Labels:** {{ issue.labels | join(", ") or "none" }}
**Assignees:** {{ issue.assignees | join(", ") or "unassigned" }}

{{ issue.body }}

Bug Dashboard

Generate a dashboard of open bugs:
models/bug-dashboard.md
---
name: Bug Dashboard
---

# Open Bugs

{% for issue in colin.github.prefect.issues(labels=["bug"], state="open") %}
## #{{ issue.number }}: {{ issue.title }}

- **Author:** {{ issue.author }}
- **Created:** {{ issue.created_at.strftime("%Y-%m-%d") }}
- **Comments:** {{ issue.comments_count }}

{{ issue.body[:200] }}{% if issue.body and issue.body|length > 200 %}...{% endif %}

{% endfor %}

PR Status Report

Track open PRs and their review status:
models/pr-status.md
---
name: Open PRs
---

# Pull Requests

{% for pr in colin.github.prefect.prs(state="open", base="main") %}
- **#{{ pr.number }}**: {{ pr.title }}
  - Branch: `{{ pr.head_ref }}` -> `{{ pr.base_ref }}`
  - Changes: +{{ pr.additions }}/-{{ pr.deletions }} in {{ pr.changed_files }} files
  {% if pr.is_draft %}(Draft){% endif %}
{% endfor %}