Security notice: Loopback API access is allowed without login. Remote web and API access is disabled unless
PATCHDECK_WEB_USERNAMEandPATCHDECK_WEB_PASSWORDare set, and then requires a signed dashboard session. Put TLS in front of the server before using remote access on an untrusted network.
- Overview
- Quick start
- Security model
- MCP server (for AI agents)
- REST API reference
- Data types
- Error handling
- Environment variables
patchdeck runs an Express HTTP server (default port 5001) that serves
both its React dashboard and a machine-readable REST API. The same API surface
is also exposed as an MCP (Model Context Protocol) server, letting any
MCP-compatible agent (Claude Desktop, OpenClaw, etc.) drive patchdeck through
structured tool calls without writing a single line of HTTP client code.
┌─────────────────────────────────┐
│ local-first machine │
│ │
│ ┌──────────┐ HTTP │
│ │ OpenClaw │──────────────┐ │
│ │ / Claude │ 127.0.0.1 │ │
│ │ Desktop │ :5001/api │ │
│ └──────────┘ ▼ │
│ │ ┌──────────┤
│ │ stdio MCP │ patchdeck │
│ └─────────────►│ server │
│ │ server │
│ └─────────┤
│ │ │
│ SQLite DB │
│ ~/.patchdeck/
└─────────────────────────────────┘
Most long-running runtime actions are durable and queue-backed. Repository sync, initial and manual babysit/apply runs, feedback retries, PR question answering, release processing, and merge-triggered deployment healing are first persisted in SQLite and then claimed by a dispatcher with leases and heartbeats. Queued work survives process restarts; on startup, expired leases are re-queued, and interrupted babysitter runs are resumed from stored run context when possible.
# development (auto-reloads)
npm run dev
# production
npm run build && npm startThe server binds to 0.0.0.0:5001 (configurable via PORT). Requests from
127.0.0.1 or ::1 can call /api/* directly. Remote requests require a web
login when PATCHDECK_WEB_USERNAME and PATCHDECK_WEB_PASSWORD are configured;
otherwise they are rejected.
If you enable deployment healing for Vercel or Railway repositories, install
and authenticate the matching platform CLI on the same machine:
vercel for Vercel and railway for Railway.
# List watched repos
curl http://localhost:5001/api/repos
# Add a PR
curl -X POST http://localhost:5001/api/prs \
-H "Content-Type: application/json" \
-d '{"url":"https://github.com/owner/repo/pull/42"}'# Start the MCP server (talks to the running patchdeck server on port 5001)
npm run mcp
# Custom port
PATCHDECK_PORT=5001 npm run mcpThe web access middleware runs before every protected /api/* handler. It
checks the resolved IP address of each incoming request:
| Source IP | Allowed? |
|---|---|
127.0.0.1 |
yes, no login required |
::1 |
yes, no login required |
::ffff:127.x.x.x |
yes, no login required |
127.x.x.x (any /8) |
yes, no login required |
| Everything else | yes only after dashboard login |
What this means in practice
- Processes running on the same machine can call the API without login.
- Docker containers, VMs, LAN peers, and the internet need a dashboard session.
- Remote login is opt-in; without credentials, non-loopback API calls receive
403. - The UI is served at
/; remote browsers see a login screen before API data loads.
Set these variables before starting the server:
PATCHDECK_WEB_USERNAME=operator
PATCHDECK_WEB_PASSWORD='choose-a-long-password'
PATCHDECK_SESSION_SECRET='choose-a-long-random-secret'PATCHDECK_SESSION_SECRET is optional; when omitted, patchdeck creates an
in-memory secret at startup and existing sessions are invalidated on restart.
Use HTTPS at the proxy/load-balancer layer before sending credentials across an
untrusted network.
The login endpoints are:
| Route | Description |
|---|---|
GET /api/auth/status |
Return whether the current caller needs login and is authenticated |
POST /api/auth/login |
Authenticate with { "username": "...", "password": "..." } |
POST /api/auth/logout |
Destroy the current dashboard session |
If you put Nginx or Caddy in front of patchdeck, make sure it forwards
X-Forwarded-For and that Express's trust proxy setting is configured
appropriately. If trust proxy is not set, req.ip will always be
127.0.0.1 (the proxy itself) — which is fine for a purely local setup but
could allow any traffic the proxy accepts if the proxy is network-facing.
The MCP server (server/mcp.ts) implements the
Model Context Protocol over stdio,
the standard transport used by Claude Desktop and most agent frameworks.
It translates every MCP tool call into an HTTP request to 127.0.0.1:5001
by default, or to the port in PATCHDECK_PORT when configured, so the
patchdeck main server must be running for any tool to work.
Add the following block to your MCP host's configuration file
(claude_desktop_config.json for Claude Desktop):
{
"mcpServers": {
"patchdeck": {
"command": "npx",
"args": ["tsx", "/absolute/path/to/patchdeck/server/mcp.ts"],
"env": {
"PATCHDECK_PORT": "5001"
}
}
}
}Replace /absolute/path/to/patchdeck with the actual path.
If you have already built the project (npm run build), you can use the
compiled output instead:
{
"mcpServers": {
"patchdeck": {
"command": "node",
"args": ["/absolute/path/to/patchdeck/dist/mcp.cjs"],
"env": {
"PATCHDECK_PORT": "5001"
}
}
}
}| Tool name | Description |
|---|---|
list_repos |
List watched repositories plus repos inferred from tracked PRs |
add_repo |
Add a repo to the watch list (defaults to My PRs only discovery) |
sync_repos |
Queue an immediate durable sync across all watched repos |
list_prs |
List all tracked pull requests |
list_archived_prs |
List archived (closed/merged) PRs |
get_pr |
Get full PR details including feedback |
add_pr |
Register a PR by GitHub URL and queue its initial babysit run |
remove_pr |
Remove a PR from tracking |
fetch_pr_feedback |
Force-refresh GitHub feedback for a PR |
triage_pr |
Auto-triage all un-triaged feedback on a PR |
apply_pr_fixes |
Queue AI work to apply accepted fixes |
babysit_pr |
Queue a full babysit cycle on a PR |
set_feedback_decision |
Manually override triage for a feedback item |
retry_feedback_item |
Retry a failed/warned feedback item |
list_pr_questions |
List Q&A history for a PR |
ask_pr_question |
Store a PR question and queue asynchronous answering |
get_logs |
Get activity logs (optional PR filter) |
get_config |
Read current configuration |
update_config |
Partially update configuration |
get_runtime |
Get runtime state (drain mode, active queue handlers) |
set_drain_mode |
Enable/disable drain mode for new queue claims |
list_changelogs |
List social-media changelogs |
get_changelog |
Get one changelog by ID |
get_onboarding_status |
Check repo onboarding status |
install_review_workflow |
Install GitHub Actions review workflow |
list_deployment_healing_sessions |
List deployment-healing sessions, optionally filtered by repo |
get_deployment_healing_session |
Get one deployment-healing session by ID |
update_config exposes the MCP schema's config subset. Use PATCH /api/config
for fields that are REST-writable but not exposed by the MCP schema today,
including release automation, CI healing, and deployment-healing keys.
Base URL: http://localhost:5001
Content-Type: application/json for all request bodies
Returns the union of explicitly watched repos and repos inferred from tracked PRs.
Use GET /api/repos/settings when you need per-repo settings such as
ownPrsOnly.
Response 200
["owner/repo-a", "owner/repo-b"]Returns repo-level settings for explicitly watched repos plus any repos inferred from currently tracked PRs.
ownPrsOnly: true means the watcher auto-discovers only PRs authored by the
authenticated GitHub user for that repo. ownPrsOnly: false enables team-wide
discovery and auto-discovers all open PRs in the repo.
Response 200 — array of WatchedRepo objects
Add a repository to the watch list.
Body
{ "repo": "owner/repo" }Accepts "owner/repo" slugs or full https://github.com/owner/repo URLs.
New watched repos default to ownPrsOnly: true (My PRs only). To switch a
repo to team-wide discovery, call PATCH /api/repos/settings after adding it.
Response 201
{ "repo": "owner/repo" }Update repo-level settings.
ownPrsOnly: true keeps auto-discovery limited to PRs authored by the
authenticated GitHub user. ownPrsOnly: false switches the repo to team-wide
auto-discovery. Changing this setting does not remove PRs that were already
tracked directly by URL.
Body
{
"repo": "owner/repo",
"ownPrsOnly": false,
"autoCreateReleases": true
}Provide repo plus one or both of ownPrsOnly and autoCreateReleases.
Response 200 — updated WatchedRepo object
Response 400 — invalid body or no settings provided
Queue an immediate durable watcher pass across all watched repos. The queued sync job performs GitHub reconciliation and then enqueues follow-up babysit and release work as needed. It does not enqueue social changelog generation. During drain mode, the sync job still refreshes repository status but does not enqueue new automation work.
Response 200
{ "ok": true }Queue a manual release run for the latest unreleased merged PR in a watched
repository. The route returns once the durable release job has been stored, not
when the release finishes. Returns 409 when drain mode is active.
Body
{ "repo": "owner/repo" }Accepts "owner/repo" slugs or full https://github.com/owner/repo URLs.
Response 201 — ReleaseRun object
Response 400 — invalid body or repository slug
Response 409 — drain mode is active, or no unreleased merged PR exists
List all actively tracked PRs with full feedback arrays.
Response 200 — array of PR objects
List archived (closed/merged) PRs.
Response 200 — array of PR objects
Get a single PR by its internal patchdeck ID.
Response 200 — PR object
Response 404 — { "error": "PR not found" }
Register a GitHub PR by URL. patchdeck fetches the PR summary from GitHub, stores it, and queues an initial durable babysit run.
This direct registration path is independent of watched-repo discovery scope,
so the PR stays tracked even if its repo is configured as ownPrsOnly: true.
Body
{ "url": "https://github.com/owner/repo/pull/42" }Response 201 — PR object (newly created)
Response 201 — PR object (already tracked)
Response 400 — invalid URL
Response 4xx — GitHub API error
Remove a PR from tracking.
Response 200 — { "ok": true }
Response 404 — not found
Pause or resume background automation for a single tracked PR.
When enabled is false, the background watcher skips autonomous sync/apply
cycles for that PR, but manual routes such as POST /api/prs/:id/fetch,
POST /api/prs/:id/triage, POST /api/prs/:id/apply, and
POST /api/prs/:id/babysit still work. Re-enabling watch schedules an
immediate watcher pass.
Body
{ "enabled": false }Response 200 — updated PR object
Response 400 — invalid body
Response 404 — PR not found
Force a fresh pull of comments and reviews from GitHub for this PR.
Response 200 — updated PR object
Run auto-triage on all un-triaged feedback items.
Classification rules (keyword-based):
- reject — contains "lgtm", "looks good"
- accept — contains "please", "should", "fix", "error", "fail"
- flag — everything else
Response 200 — updated PR object
Queue the configured AI agent to apply all accepted feedback in an isolated git
worktree. The route returns once the durable job has been stored, not when the
agent run completes. Returns 409 when drain mode is active.
Response 200 — updated PR object
Queue a full babysit cycle: sync → triage → apply → report. The route returns
once the durable job has been stored, not when the run finishes. Returns 409
when drain mode is active.
Response 200 — updated PR object
Manually override the triage decision for a single feedback item.
Body
{ "decision": "accept" }Valid values: "accept" | "reject" | "flag"
Response 200 — updated PR object
Re-queue a failed or warned feedback item by scheduling another durable
babysit pass for the PR. Returns 409 when drain mode is active; the feedback
item is left unchanged and no babysit job is queued.
Response 200 — updated PR object
Response 404 — PR or feedback item not found
Response 400 — item is not in a retryable state
Response 409 — drain mode is active
List the full question/answer history for a PR.
Response 200 — array of PRQuestion objects
Ask the configured AI agent a question about the PR. The question is stored
immediately; a durable background job fills in the answer asynchronously. Both
the question row and the queued answer job persist in SQLite, so pending
questions survive app restarts. Returns 409 when drain mode is active, before
storing the question or queueing the answer job.
Body
{ "question": "Why does the linter keep failing on line 42?" }Maximum 2000 characters.
Response 201 — PRQuestion object (answer will be null until the agent responds)
Response 409 — drain mode is active
Retrieve activity logs, optionally filtered by PR.
Query parameters
| Parameter | Type | Description |
|---|---|---|
prId |
string | Filter to a single PR (optional) |
Response 200 — array of LogEntry objects
Retrieve recent structured server log records from the in-memory ring buffer
that backs the dashboard /logs page.
Query parameters
| Parameter | Type | Description |
|---|---|---|
level |
string | Minimum level: trace, debug, info, warn, error, or fatal |
source |
string | Filter to one structured log source |
since |
number | Return records with a sequence number greater than this cursor |
search |
string | Case-insensitive match against the message or structured fields |
limit |
number | Maximum number of records to return; defaults to 500 |
Response 200
{
"records": [],
"sources": ["babysitter", "github"],
"latestSeq": 123
}Tail server logs as Server-Sent Events for the dashboard /logs page. Pass
since to replay up to the latest 1000 missed records before live events
begin. Slow clients may be disconnected rather than allowing unbounded response
buffering.
List all persisted healing sessions, newest first.
Response 200 — array of HealingSession objects
Get one healing session by its internal patchdeck ID.
Response 200 — HealingSession object
Response 404 — { "error": "Healing session not found" }
Deployment-healing sessions are created when a merged PR belongs to a detected Vercel or Railway repository and deployment healing is enabled. The background job waits for the deployment, captures failure logs when the deployment enters an error state, and records the repair outcome.
List persisted deployment-healing sessions, newest first.
Query parameters
| Parameter | Type | Description |
|---|---|---|
repo |
string | Optional repository slug (owner/repo) filter |
Response 200 — array of DeploymentHealingSession objects
Get one deployment-healing session by its internal patchdeck ID.
Response 200 — DeploymentHealingSession object
Response 404 — { "error": "Deployment healing session not found" }
Read the current configuration. GitHub tokens are redacted to ordered ***xxxx
values.
Response 200 — Config object (tokens redacted)
Partially update the configuration. Only the provided fields are changed.
Body (all fields optional)
{
"githubTokens": ["ghp_xxxxxxxxxxxx", "github_pat_yyyyyyyyyyyy"],
"codingAgent": "claude",
"maxTurns": 15,
"batchWindowMs": 300000,
"pollIntervalMs": 120000,
"maxChangesPerRun": 10,
"autoResolveMergeConflicts": true,
"autoCreateReleases": true,
"autoUpdateDocs": true,
"githubCommentAppName": "patchdeck",
"includeRepositoryLinksInGitHubComments": true,
"postGitHubProgressReplies": false,
"autoHealCI": false,
"maxHealingAttemptsPerSession": 3,
"maxHealingAttemptsPerFingerprint": 2,
"maxConcurrentHealingRuns": 1,
"healingCooldownMs": 300000,
"autoHealDeployments": false,
"deploymentCheckDelayMs": 60000,
"deploymentCheckTimeoutMs": 600000,
"deploymentCheckPollIntervalMs": 15000,
"watchedRepos": ["owner/repo"],
"trustedReviewers": ["alice", "bob"],
"priorityIssueAuthors": ["octocat"],
"ignoredBots": ["dependabot", "codecov"]
}Response 200 — updated Config object (tokens redacted)
Some configuration is REST-writable but not exposed by the MCP update_config
tool schema today. Use this REST endpoint when changing release automation,
CI-healing, or deployment-healing keys.
priorityIssueAuthors accepts GitHub account logins such as octocat; values
are matched case-insensitively and may include or omit a leading @. Issues from
matching authors are evaluated and worked before the regular issue queue, and
their evaluation/work jobs use elevated background-job priority. The default is
an empty array, which leaves issue ordering and job priority unchanged.
Return the release-check result used by the dashboard update banner.
The server reads the running app version from APP_VERSION and falls back to
"dev" when unset. If the current version is a stable semver string, patchdeck
checks the latest stable GitHub release for jeremymcs/patchdeck, ignores draft
and prerelease releases, and compares the versions. If the current build is not
semver or the GitHub release check fails, the endpoint returns a quiet fallback
with latestVersion: null, the releases index URL, and updateAvailable: false.
Response 200 — AppUpdateStatus object
Example 200 — newer release available
{
"currentVersion": "1.0.0",
"latestVersion": "v1.1.0",
"latestReleaseUrl": "https://github.com/jeremymcs/patchdeck/releases/tag/v1.1.0",
"updateAvailable": true
}Example 200 — quiet fallback for a non-versioned build or failed check
{
"currentVersion": "dev",
"latestVersion": null,
"latestReleaseUrl": "https://github.com/jeremymcs/patchdeck/releases",
"updateAvailable": false
}Runtime state is queue-aware. activeRuns counts currently executing queue
handlers (leased jobs); jobs still waiting in SQLite are not included.
Get the current runtime state.
Response 200
{
"drainMode": false,
"drainRequestedAt": null,
"drainReason": null,
"activeRuns": 2
}Enable or disable drain mode. When enabled, the dispatcher stops claiming new
automation jobs but still claims sync_watched_repos jobs so repository status
can refresh. Other queued rows remain intact in SQLite. Already-running
handlers are allowed to finish, and waitForIdle also waits on any started
babysitter or release work before reporting success.
patchdeck may also enable drain mode automatically when an agent health check
reports deterministic unavailability, such as a missing CLI, auth failure, or
unsupported agent setting. Transient health-check failures, including command
timeouts, skip the affected automation cycle with an Automation skipped log
instead of setting drainMode.
Manual agent-triggering endpoints return 409 instead of storing new work
while drain mode is active. This includes dashboard Run now
(POST /api/prs/:id/apply), POST /api/prs/:id/babysit, feedback retry,
PR Q&A, manual repository release, and release retry. Drain-safe routes such
as POST /api/repos/sync may still enqueue status refresh work that does not
start new agent automation.
Body
{
"enabled": true,
"reason": "Deploying new version",
"waitForIdle": true,
"timeoutMs": 120000
}| Field | Type | Required | Description |
|---|---|---|---|
enabled |
boolean | yes | true to enable drain mode, false to disable |
reason |
string | no | Human-readable reason (stored in state) |
waitForIdle |
boolean | no | Wait until queue workers and started babysitter/release runs go idle |
timeoutMs |
number | no | Max wait in ms when waitForIdle is true (≤600s) |
Response 200 — drained successfully (or disabled)
Response 202 — drain enabled but timed out before idle
Social media changelog generation has been removed. These endpoints remain read-only compatibility surfaces for historical changelog rows.
List all generated social-media changelog posts.
Response 200 — array of SocialChangelog objects
Get a single social-media changelog by ID.
Response 200 — SocialChangelog object
Response 404 — not found
Release evaluation and publishing are also durable background jobs. When a
tracked PR is archived as merged and automatic releases are enabled, patchdeck
persists a release run and queues background processing. Retrying a
failed release run resets it to detected and re-queues processing.
List all persisted release runs, newest first.
Response 200 — array of ReleaseRun objects
Get one release run by its internal patchdeck ID.
Response 200 — ReleaseRun object
Response 404 — not found
Reset an existing release run to detected and queue it for durable
reprocessing. Returns 409 when drain mode is active; the release run is left
unchanged and no release job is queued.
Response 200 — updated ReleaseRun object
Response 404 — not found
Response 409 — drain mode is active
Check the onboarding status of all watched repositories (e.g., whether the patchdeck GitHub Actions workflow is installed).
Response 200
{
"githubConnected": true,
"githubUser": "octocat",
"repos": [
{
"repo": "owner/repo",
"accessible": true,
"codeReviews": {
"claude": true,
"codex": false,
"gemini": false
}
}
]
}Install the patchdeck code-review GitHub Actions workflow on a repository.
Body
{
"repo": "owner/repo",
"tool": "claude"
}tool must be "claude" or "codex".
Response 200
{
"path": ".github/workflows/claude-code-review.yml",
"url": "https://github.com/owner/repo/blob/main/.github/workflows/claude-code-review.yml"
}{
id: string; // Internal UUID
number: number; // GitHub PR number
title: string;
repo: string; // "owner/repo"
branch: string;
author: string;
url: string; // Full GitHub URL
status: "watching" | "processing" | "done" | "error" | "archived";
feedbackItems: FeedbackItem[];
accepted: number; // Count of accepted items
rejected: number;
flagged: number;
testsPassed: boolean | null;
lintPassed: boolean | null;
lastChecked: string | null; // ISO 8601
watchEnabled: boolean; // false pauses autonomous watcher runs for this PR
docsAssessment?: {
headSha: string;
status: "needed" | "not_needed" | "failed";
summary: string;
assessedAt: string; // ISO 8601
} | null;
addedAt: string; // ISO 8601
}{
id: string;
author: string;
body: string;
bodyHtml: string;
replyKind: "review_thread" | "review" | "general_comment";
sourceId: string;
sourceNodeId: string | null;
sourceUrl: string | null;
threadId: string | null;
threadResolved: boolean | null;
auditToken: string;
file: string | null;
line: number | null;
type: "review_comment" | "review" | "general_comment";
createdAt: string; // ISO 8601
decision: "accept" | "reject" | "flag" | null;
decisionReason: string | null;
action: string | null;
status: "pending" | "queued" | "in_progress" | "resolved" | "failed" | "warning" | "rejected" | "flagged";
statusReason: string | null;
}{
id: string;
prId: string;
question: string;
answer: string | null; // null until the agent responds
status: "pending" | "answering" | "answered" | "error";
error: string | null;
createdAt: string; // ISO 8601
answeredAt: string | null; // ISO 8601
}{
id: string;
prId: string;
runId: string | null;
timestamp: string; // ISO 8601
level: "info" | "warn" | "error";
phase: string | null;
message: string;
metadata: Record<string, unknown> | null;
}{
githubTokens: string[]; // Ordered and redacted to "***xxxx" in GET responses
githubToken?: string; // Legacy single-token field, redacted when present
codingAgent: "claude" | "codex";
maxTurns: number;
batchWindowMs: number;
pollIntervalMs: number;
maxChangesPerRun: number;
autoResolveMergeConflicts: boolean;
autoCreateReleases: boolean;
autoUpdateDocs: boolean;
githubCommentAppName: string; // Defaults to "patchdeck"; blank removes the GitHub reply signature
includeRepositoryLinksInGitHubComments: boolean;
postGitHubProgressReplies: boolean;
autoHealCI: boolean;
maxHealingAttemptsPerSession: number;
maxHealingAttemptsPerFingerprint: number;
maxConcurrentHealingRuns: number;
healingCooldownMs: number;
autoHealDeployments: boolean;
deploymentCheckDelayMs: number;
deploymentCheckTimeoutMs: number;
deploymentCheckPollIntervalMs: number;
watchedRepos: string[];
trustedReviewers: string[];
priorityIssueAuthors: string[]; // GitHub logins whose issues receive queue and job priority; empty by default
ignoredBots: string[];
}{
repo: string; // "owner/repo"
autoCreateReleases: boolean;
ownPrsOnly: boolean; // true => only auto-discover the authenticated user's PRs
}{
currentVersion: string; // APP_VERSION or "dev"
latestVersion: string | null; // latest stable GitHub release tag, e.g. "v1.1.0"
latestReleaseUrl: string; // release page or releases index fallback
updateAvailable: boolean; // true when latestVersion is newer than currentVersion
}{
drainMode: boolean;
drainRequestedAt: string | null; // ISO 8601
drainReason: string | null;
activeRuns: number; // currently executing durable queue jobs
}{
id: string;
prId: string;
repo: string; // "owner/repo"
prNumber: number;
initialHeadSha: string;
currentHeadSha: string;
state:
| "idle"
| "triaging"
| "awaiting_repair_slot"
| "repairing"
| "awaiting_ci"
| "verifying"
| "healed"
| "cooldown"
| "blocked"
| "escalated"
| "superseded";
startedAt: string; // ISO 8601
updatedAt: string; // ISO 8601
endedAt: string | null; // ISO 8601
blockedReason: string | null;
escalationReason: string | null;
latestFingerprint: string | null;
attemptCount: number;
lastImprovementScore: number | null;
}{
id: string;
repo: string; // "owner/repo"
platform: "vercel" | "railway";
triggerPrNumber: number;
triggerPrTitle: string;
triggerPrUrl: string;
mergeSha: string;
deploymentId: string | null;
deploymentLog: string | null;
fixBranch: string | null;
fixPrNumber: number | null;
fixPrUrl: string | null;
state: "monitoring" | "failed" | "fixing" | "fix_submitted" | "escalated";
error: string | null;
createdAt: string; // ISO 8601
updatedAt: string; // ISO 8601
completedAt: string | null; // ISO 8601
}{
id: string;
repo: string; // "owner/repo"
baseBranch: string;
triggerPrNumber: number;
triggerPrTitle: string;
triggerPrUrl: string;
triggerMergeSha: string;
triggerMergedAt: string; // ISO 8601
status: "detected" | "evaluating" | "skipped" | "proposed" | "publishing" | "published" | "error";
decisionReason: string | null;
recommendedBump: "patch" | "minor" | "major" | null;
proposedVersion: string | null;
releaseTitle: string | null;
releaseNotes: string | null;
includedPrs: Array<{
number: number;
title: string;
url: string;
author: string;
mergedAt: string; // ISO 8601
mergeSha: string;
}>;
targetSha: string | null;
githubReleaseId: number | null;
githubReleaseUrl: string | null;
error: string | null;
createdAt: string; // ISO 8601
updatedAt: string; // ISO 8601
completedAt: string | null; // ISO 8601
}{
id: string;
date: string; // "YYYY-MM-DD"
triggerCount: number; // nth merge that triggered this (5, 10, 15…)
prSummaries: Array<{
number: number;
title: string;
url: string;
author: string;
repo: string;
}>;
content: string | null; // Generated social-media post
status: "generating" | "done" | "error";
error: string | null;
createdAt: string; // ISO 8601
completedAt: string | null; // ISO 8601
}All error responses share this shape:
{ "error": "Human-readable description" }| Status | Meaning |
|---|---|
400 |
Validation error (bad input) |
401 |
Login required or invalid credentials |
403 |
Request blocked — remote login is not configured |
404 |
Resource not found |
409 |
Conflict — e.g. drain mode is active |
500 |
Internal server error |
| Variable | Default | Description |
|---|---|---|
PORT |
5001 |
HTTP port for the patchdeck server |
PATCHDECK_PORT |
5001 |
Port the MCP server connects to (MCP only) |
PATCHDECK_HOME |
~/.patchdeck |
Directory for SQLite DB, logs, repos, worktrees |
CODEFACTORY_HOME |
— | Legacy alias used only when PATCHDECK_HOME is not set |
PATCHDECK_WEB_USERNAME |
— | Username for opt-in remote dashboard/API login |
PATCHDECK_WEB_PASSWORD |
— | Password for opt-in remote dashboard/API login |
PATCHDECK_SESSION_SECRET |
generated at startup | Secret for signing remote dashboard sessions |
GITHUB_TOKEN |
— | Fallback GitHub token after saved app tokens and before gh auth |
NODE_ENV |
development |
Set to production for production builds |
TAURI_DEV |
— | Set to skip auto-opening the browser (used by Tauri) |
The MCP server also accepts CODEFACTORY_PORT as a legacy fallback when PATCHDECK_PORT is unset.