Fix JSON syntax errors in .devcontainer/devcontainer.json (removed
invalid JS-style // comments) and .devcontainer/opencode.json (removed
90+ trailing commas). Apply auto-fixes for end-of-file and trailing
whitespace issues across 100+ files. Fix SIM105 ruff violations in
benchmarks/core_circuit_breaker_bench.py (use contextlib.suppress).
Note: The security fix from issue #7478 (validate_path startswith bypass)
was already delivered to master in commit e18ac5f2. This PR as currently
structured is non-atomic (35 commits across 10+ issues) and needs
significant restructure before merge. This commit only addresses the
CI/pre-commit failures.
ISSUES CLOSED: #7478
Pull Requests API Reference
Core Endpoints
List Pull Requests
GET /repos/{owner}/{repo}/pulls
Parameters:
| Parameter | Type | Description |
|---|---|---|
| state | string | Filter by state: open, closed, all |
| sort | string | Sort by: oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority |
| milestone | int | Filter by milestone ID |
| labels | string | Comma-separated label IDs |
| poster | string | Filter by PR author (username) |
| page | int | Page number (1-based) |
| limit | int | Page size (default varies by server config) |
# List open PRs
curl -s \
-H "Authorization: token ${FORGEJO_PAT}" \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls?state=open&limit=10"
# List closed PRs sorted by most recently updated
curl -s \
-H "Authorization: token ${FORGEJO_PAT}" \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls?state=closed&sort=recentupdate&page=1&limit=25"
# Filter by milestone and labels
curl -s \
-H "Authorization: token ${FORGEJO_PAT}" \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls?state=open&milestone=3&labels=5,12"
Create a Pull Request
POST /repos/{owner}/{repo}/pulls
CreatePullRequestOption:
| Field | Type | Required | Description |
|---|---|---|---|
| title | string | yes | PR title |
| body | string | no | PR description (markdown) |
| head | string | yes | Source branch name |
| base | string | yes | Target branch name |
| assignee | string | no | Single assignee username |
| assignees | array[string] | no | Multiple assignee usernames |
| labels | array[int] | no | Label IDs to apply |
| milestone | int | no | Milestone ID |
| due_date | string | no | Due date (ISO 8601 format) |
# Create a simple PR
curl -s -X POST \
-H "Authorization: token ${FORGEJO_PAT}" \
-H "Content-Type: application/json" \
-d '{
"title": "Add user authentication",
"body": "## Summary\nImplements JWT-based auth.\n\nCloses #42",
"head": "{head_branch}",
"base": "{base_branch}"
}' \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls"
# Create a PR with assignees, labels, and milestone
curl -s -X POST \
-H "Authorization: token ${FORGEJO_PAT}" \
-H "Content-Type: application/json" \
-d '{
"title": "Refactor database layer",
"body": "Switches from raw SQL to ORM queries.",
"head": "{head_branch}",
"base": "{base_branch}",
"assignees": ["{assignee1}", "{assignee2}"],
"labels": [1, 5, 8],
"milestone": 2,
"due_date": "2025-03-15T00:00:00Z"
}' \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls"
Get Pull Request Details
GET /repos/{owner}/{repo}/pulls/{index}
curl -s \
-H "Authorization: token ${FORGEJO_PAT}" \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}"
Update a Pull Request
PATCH /repos/{owner}/{repo}/pulls/{index}
Accepts the same fields as create (title, body, base, assignee, assignees, labels, milestone, due_date) plus:
| Field | Type | Description |
|---|---|---|
| state | string | Change state: open or closed |
| allow_maintainer_edit | boolean | Allow maintainers to push to head |
# Update title and body
curl -s -X PATCH \
-H "Authorization: token ${FORGEJO_PAT}" \
-H "Content-Type: application/json" \
-d '{
"title": "Add user authentication (v2)",
"body": "Updated implementation using OAuth2."
}' \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}"
# Close a PR
curl -s -X PATCH \
-H "Authorization: token ${FORGEJO_PAT}" \
-H "Content-Type: application/json" \
-d '{"state": "closed"}' \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}"
# Allow maintainer edits
curl -s -X PATCH \
-H "Authorization: token ${FORGEJO_PAT}" \
-H "Content-Type: application/json" \
-d '{"allow_maintainer_edit": true}' \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}"
List Pinned Pull Requests
GET /repos/{owner}/{repo}/pulls/pinned
curl -s \
-H "Authorization: token ${FORGEJO_PAT}" \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/pinned"
Get PR Between Specific Branches
GET /repos/{owner}/{repo}/pulls/{base}/{head}
Returns the PR (if any) that merges head into base.
curl -s \
-H "Authorization: token ${FORGEJO_PAT}" \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{base}/{head}"
Get Diff or Patch
GET /repos/{owner}/{repo}/pulls/{index}.diff
GET /repos/{owner}/{repo}/pulls/{index}.patch
Returns raw diff or patch format (plain text, not JSON).
# Get raw diff
curl -s \
-H "Authorization: token ${FORGEJO_PAT}" \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}.diff"
# Get patch format
curl -s \
-H "Authorization: token ${FORGEJO_PAT}" \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}.patch"
List PR Commits
GET /repos/{owner}/{repo}/pulls/{index}/commits
curl -s \
-H "Authorization: token ${FORGEJO_PAT}" \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/commits"
Update PR Branch (Server-Side Rebase/Merge)
POST /repos/{owner}/{repo}/pulls/{index}/update
Merges or rebases the base branch into the PR's head branch without needing a local clone. Only works when there are no conflicts.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
| style | string | rebase or merge (default: merge) |
Note: The style parameter is a query parameter per the Swagger spec. Some Forgejo versions also accept {"style": "rebase"} as a JSON body, but the canonical way is as a query parameter.
# Update PR branch by merging base into head (default)
curl -s -X POST \
-H "Authorization: token ${FORGEJO_PAT}" \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/update?style=merge"
# Update PR branch by rebasing onto base
curl -s -X POST \
-H "Authorization: token ${FORGEJO_PAT}" \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/update?style=rebase"
PullRequest Response Object
Key Fields
| Field | Type | Description |
|---|---|---|
| number | int | PR index number |
| title | string | PR title |
| body | string | PR description (markdown) |
| state | string | open or closed |
| draft | boolean | Whether this is a draft PR |
| mergeable | boolean/null | Whether the PR can be merged (null = not yet computed) |
| merged | boolean | Whether the PR has been merged |
| merged_at | string/null | Timestamp of merge (ISO 8601) |
| merged_by | User/null | User who performed the merge |
| base | PRBranchInfo | Target branch info |
| head | PRBranchInfo | Source branch info |
| additions | int | Total lines added |
| deletions | int | Total lines deleted |
| changed_files | int | Number of files changed |
| comments | int | Number of general comments |
| review_comments | int | Number of review/inline comments |
| labels | array[Label] | Applied labels |
| milestone | Milestone | Associated milestone |
| assignees | array[User] | Assigned users |
| requested_reviewers | array[User] | Users requested for review |
| requested_reviewers_teams | array[Team] | Teams requested for review |
| merge_base | string | Merge base commit SHA |
| merge_commit_sha | string | Merge commit SHA (if merged) |
| diff_url | string | URL to raw diff |
| patch_url | string | URL to patch format |
| allow_maintainer_edit | boolean | Whether maintainers can push to head branch |
| pin_order | int | Pin order (0 = not pinned) |
| flow | string | PR flow type |
PRBranchInfo Fields
| Field | Type | Description |
|---|---|---|
| label | string | Branch label (owner:branch) |
| ref | string | Branch name |
| sha | string | Current HEAD SHA of the branch |
| repo_id | int | Repository ID |
| repo | Repository | Full repository object |
Checking PR Status
Check if a PR is a Draft
PR=$(curl -s \
-H "Authorization: token ${FORGEJO_PAT}" \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}")
IS_DRAFT=$(echo "$PR" | jq '.draft')
echo "Is draft: $IS_DRAFT"
Check if a PR Has Conflicts (Not Mergeable)
The mergeable field indicates whether the PR can be cleanly merged:
true- no conflicts, can mergefalse- has conflicts, cannot mergenull- mergeability not yet computed (Forgejo computes this lazily)
MERGEABLE=$(echo "$PR" | jq '.mergeable')
if [ "$MERGEABLE" = "false" ]; then
echo "PR has merge conflicts!"
elif [ "$MERGEABLE" = "null" ]; then
echo "Mergeability not yet computed. Try again shortly."
else
echo "PR is mergeable."
fi
Check if a PR is Stale (Base Branch Has Advanced)
A PR is "stale" when the base branch has new commits that are not in the PR's branch. Compare pr.base.sha (the base branch SHA at the time the PR object was generated) with the current HEAD of the base branch:
# Get the PR's recorded base SHA
PR_BASE_SHA=$(echo "$PR" | jq -r '.base.sha')
# Get the current HEAD of the base branch
BASE_BRANCH=$(echo "$PR" | jq -r '.base.ref')
CURRENT_BASE_SHA=$(curl -s \
-H "Authorization: token ${FORGEJO_PAT}" \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/branches/${BASE_BRANCH}" | jq -r '.commit.id')
if [ "$PR_BASE_SHA" != "$CURRENT_BASE_SHA" ]; then
echo "PR is stale. Base branch has advanced."
echo "PR base SHA: $PR_BASE_SHA"
echo "Current base SHA: $CURRENT_BASE_SHA"
# Optionally update the PR branch server-side
curl -s -X POST \
-H "Authorization: token ${FORGEJO_PAT}" \
"${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/update?style=rebase"
else
echo "PR is up to date with base branch."
fi
Combined Status Check
#!/bin/bash
# Full PR health check
OWNER="{owner}"
REPO="{repo}"
PR_INDEX="{index}"
PR=$(curl -s \
-H "Authorization: token ${FORGEJO_PAT}" \
"${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/pulls/${PR_INDEX}")
echo "PR #${PR_INDEX}: $(echo "$PR" | jq -r '.title')"
echo "State: $(echo "$PR" | jq -r '.state')"
echo "Draft: $(echo "$PR" | jq '.draft')"
echo "Merged: $(echo "$PR" | jq '.merged')"
echo "Mergeable: $(echo "$PR" | jq '.mergeable')"
echo "Changes: +$(echo "$PR" | jq '.additions') -$(echo "$PR" | jq '.deletions') in $(echo "$PR" | jq '.changed_files') files"
echo "Comments: $(echo "$PR" | jq '.comments') general, $(echo "$PR" | jq '.review_comments') review"
# Check staleness
PR_BASE_SHA=$(echo "$PR" | jq -r '.base.sha')
BASE_BRANCH=$(echo "$PR" | jq -r '.base.ref')
CURRENT_BASE=$(curl -s \
-H "Authorization: token ${FORGEJO_PAT}" \
"${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/branches/${BASE_BRANCH}" | jq -r '.commit.id')
if [ "$PR_BASE_SHA" != "$CURRENT_BASE" ]; then
echo "Status: STALE (base branch has new commits)"
else
echo "Status: Up to date"
fi