chore/merge upstream to gynzy#22
Merged
Merged
Conversation
…ue-configurable feat: make repo init timeout configurable via OPTIO_REPO_INIT_TIMEOUT_MS
PVC mounted at /home/agent was root-owned on GKE, causing "Permission denied" when agent user tried to write ~/.gitconfig or create worktrees. - Add pod-level securityContext (fsGroup/runAsUser/runAsGroup=1000) to StatefulSet pods so mounted volumes are writable by the agent user - Create agent user with explicit UID/GID 1000 in base Dockerfile (Ubuntu 24.04 ships 'ubuntu' at UID 1000, pushing 'agent' to 1001) Closes jonwiggins#424
…onwiggins#425) Allow users to configure a custom base URL for local/self-hosted inference servers (vLLM, lightllm, Ollama, etc.) when using the OpenCode agent. When a base URL is set, provider API keys are optional — a placeholder key is injected so keyless local endpoints work out of the box. Closes jonwiggins#425
Build each image on native runners (ubuntu-latest for amd64, ubuntu-24.04-arm for arm64) in parallel, push per-arch by digest, then merge into a single multi-arch manifest via `docker buildx imagetools create`. Applies to optio-api, optio-web, optio-optio, and all optio-agent-* presets. Closes jonwiggins#426
Every unit of agent work is now a Task. A Task has an optional
"Attach a repo" toggle: when set, it becomes a Repo Task that opens
a PR; when not, it's a Standalone Task that runs the agent with no
checkout. Both share the same trigger, template, and connection
primitives.
Unified trigger model
- workflow_triggers polymorphic via (target_type, target_id)
- task_configs table stores reusable Repo Task blueprints
- Trigger worker dispatches to createWorkflowRun or instantiateTask
- Ticket sync fires task_config triggers on actionable tickets
- Webhook ingress (/api/hooks/:path) dispatches by target_type
Templates
- prompt_templates gains kind (prompt/review/job/task), params_schema,
default_agent_type, description
- renderTemplateString() supports {{param}} and {{#if}} blocks
- /templates library page with editor, preview, kind filter
UI
- Sidebar grouped into Run / Library / Insights / Admin
- /tasks/new is a single unified form (Who/What/When/Where/Why)
- /tasks/scheduled list + detail pages with per-trigger pause/resume
- Shared TriggerSelector component for cron/webhook configuration
- Jobs page retitled "Standalone Tasks"; /jobs/new redirects to /tasks/new
Docs
- CLAUDE.md rewritten for the unified model
- /docs/guides/workflows retitled "Standalone Tasks"
- New /docs/guides/scheduled-tasks guide
- API reference paths updated to /api/jobs and /api/task-configs
Also includes follow-up OpenCode work from parallel branch:
- Custom OpenCode base URL via OPENCODE_DEFAULT_BASE_URL secret
- OpenCode adapter ungated (OPTIO_OPENCODE_ENABLED removed)
- Setup wizard step for OpenCode custom endpoints
1917 API tests pass; 242 web tests pass; production build clean.
Mirrors the Repo Tasks filter bar (search, time range, agent type, cost min/max) with client-side filtering and URL state sync. Stage and repo filters are omitted — definitions have no pipeline state, and standalones are repo-agnostic.
All three task kinds — repo-task (ad-hoc), repo-blueprint (scheduled
Repo Task config), and standalone (Standalone Task) — are now reachable
through one polymorphic /api/tasks resource. The existing tables
(tasks, task_configs, workflows, workflow_runs) stay distinct because
their lifecycle and column shapes genuinely differ; only the HTTP
surface is unified.
Backend
- unified-task-service: resolveAnyTaskById() checks tasks → task_configs
→ workflows; UUIDs are globally unique so no collision. Plus
listUnifiedTasks, listUnifiedRuns, getUnifiedRun, list/getTriggersForParent.
- GET /api/tasks now accepts ?type=repo-task|repo-blueprint|standalone|all.
Default preserves existing enriched repo-task shape.
- POST /api/tasks accepts { type } discriminator and dispatches to
taskService, taskConfigService, or workflowService. Default preserves
existing repo-task behavior.
- GET /api/tasks/:id falls through to the resolver when the id isn't a
tasks row — returns the native row tagged with a `type` field.
- NEW routes at /api/tasks/:id/runs[/:runId] and /api/tasks/:id/triggers
polymorphic over blueprints + standalone; 405 for ad-hoc repo-tasks.
Frontend
- api-client: added listTasksUnified, createTaskUnified, getTaskUnified,
listTaskRuns, createTaskRun, getTaskRun, listTaskTriggers,
createTaskTrigger, updateTaskTrigger, deleteTaskTrigger.
- /tasks/new rewritten to call createTaskUnified with the right type based
on the mode + run-mode selection — one branch instead of four.
Docs
- CLAUDE.md: added Unified /api/tasks HTTP layer section.
- api-reference: rewrote the Tasks section to show the polymorphic
endpoints and document the type discriminator.
Legacy /api/jobs/* and /api/task-configs/* still work as thin aliases
for back-compat.
1930 API tests pass (13 new for the unified routes). 242 web tests pass.
Production build clean.
Found during end-to-end testing of the unified /api/tasks surface. Each bug pre-existed my unification work but was exposed by actually driving traffic through the full pipeline. 1. createWorkflowRun never enqueued workflow-service.createWorkflowRun inserted a workflow_runs row but did NOT enqueue a BullMQ job on workflowRunQueue — only the /api/jobs/:id/runs HTTP route did. This meant schedule triggers, webhook ingress, and the new /api/tasks/:id/runs endpoint all created runs that sat in queued forever. Moved the enqueue into the service so every caller gets it automatically; removed the duplicate enqueue from workflows.ts. 2. runAsUser: 1000 doesn't match image's agent UID 1001 0a32e34 set fsGroup/runAsUser/runAsGroup to 1000 on StatefulSets, but the base image creates `agent` at UID 1001 and chowns /workspace to 1001. Pods ran as 1000 and got Permission denied cloning the repo. Changed all three fields to 1001. 3. No initContainer to chown the PVC mount Some storage classes (docker-desktop hostpath, GKE standard) don't honor fsGroup. Added a home-perm-fix initContainer that runs as root and chown -R 1001:1001 /home/agent before the main container starts. 4. 120s pod-ready timeout too tight for local dev Extracted POD_READY_TIMEOUT_MS to OPTIO_POD_READY_TIMEOUT_MS env var, default 300s. First-ever agent pod on docker-desktop often takes longer than 120s (image pull + init container + scheduling). 5. waitForJobPod used the wrong label selector Looked up workflow pods with labelSelector: job-name=X, but the pods are labeled app.kubernetes.io/instance=X (our template, not the standard K8s Job controller label). Function ALWAYS timed out. Switched to the instance label we actually set. Bonus: the OAuth token bug surfaced here too, but that's user data in the secret store (fix: POST /api/secrets with a fresh token). Not included in this commit. Verified end-to-end: R1 repo-task ran through focus_center (clone, agent edits, completion). R2 standalone run completed in ~10s with \$0.062 cost. R3 scheduled trigger fired and dispatched correctly.
Docs drift cleanup after the Task unification and /api/tasks polymorphic endpoint shipped. - docs landing: pitch rewritten around Task (with optional repo) instead of "workflow orchestration + agent workflows" - creating-tasks: adds the New Task mode picker (Repo Task / Standalone Task), the "Run now vs Schedule" toggle, adapted button labels, and where each kind surfaces in the UI - scheduled-tasks: all API examples moved to /api/tasks with the type discriminator (repo-blueprint, standalone); callout now describes both Repo + Standalone as schedulable; legacy paths called out as aliases - workflows (Standalone Tasks) guide: code examples use /api/tasks with type: "standalone" and /api/tasks/:id/runs + /api/tasks/:id/triggers instead of /api/jobs/* No code changes. Typecheck clean.
…onwiggins#446) When a workflow pod dies (OOM, crashloop, node drain) the corresponding workflow_runs row stays in 'running' forever — blocking concurrency and confusing the UI. Add periodic zombie detection that checks pod liveness for stale running runs and transitions them to failed with retry support. Also adds orphaned repo task detection for tasks whose pod record has been cleaned up but the task was never transitioned out of running. Both checks run as part of the existing repo-cleanup-worker cycle. Co-authored-by: Optio Agent <optio-agent@noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lone-tasks (jonwiggins#440) The guide page was retitled to "Standalone Tasks" but the URL slug still used the legacy "workflows" path. This renames the route directory, updates all nav/sitemap/cross-link references, and adds a client-side redirect page at the old path so existing external links don't 404. - Rename workflows/ directory to standalone-tasks/ - Update docs.ts nav href - Update sitemap.ts entry - Update cross-links in connections and integrations guide pages - Add redirect page at old /docs/guides/workflows path - Add vitest + tests for docs nav and sitemap entries Co-authored-by: Optio Agent <optio-agent@noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…onwiggins#439) Replace the stale Schedules section (leftover from removed feature) and the separate Workflows section (listing /api/jobs/* as primary API) with a single "Legacy Aliases" note that points readers to the canonical /api/tasks polymorphic surface. Closes jonwiggins#436 Co-authored-by: Optio Agent <optio-agent@noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…onwiggins#445) - Add last_error, last_error_at, consecutive_failures columns to ticket_providers table so sync errors are persisted per-provider - On successful sync, clear error state; on failure, record the error message and increment consecutive failure count - Auto-disable provider after 5 consecutive failures to stop log spam - Downgrade repeated identical errors to debug level logging - Add PATCH /api/tickets/providers/:id/re-enable endpoint to clear errors and re-enable a disabled provider - Update Settings > Ticket Providers UI to show error details, disabled state, failure count, and a "Re-enable" button - Add migration and journal entry for the new columns Closes jonwiggins#438 Co-authored-by: Optio Agent <optio-agent@noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…iggins#449) Delete the FailureInsights and PerformanceSummary components from the main dashboard. The underlying API endpoints remain since they are still used by the /analytics page. Closes jonwiggins#428 Co-authored-by: Optio Agent <optio-agent@noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…#450) Remove the conditional failed-tasks banner that prompted users to ask Optio to investigate. Also removes now-unused Bot icon import and useOptioChatStore hook from the dashboard page. Adds tests verifying the banner no longer appears. Closes jonwiggins#429 Co-authored-by: Optio Agent <optio-agent@noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add proactive detection and surfacing of expired Claude OAuth tokens so users see warnings before tasks fail with opaque 401 errors. Changes: - New token-validation-worker: BullMQ repeating job (every 5min) that validates the stored CLAUDE_CODE_OAUTH_TOKEN against the Anthropic API and caches the result in Redis. Publishes auth:failed WebSocket event when expired. - Pre-flight check in task-worker: reads the cached validation before pod provisioning to fail fast with a clear error instead of wasting ~10s of infra work per task. - Auth status endpoint extended with lastValidated timestamp from the background worker's cache, avoiding redundant API probes on each poll. - Global auth warning banner in layout-shell: visible on all pages (not just the dashboard) when the token is expired, with a direct link to the Secrets page. - Improved error-classifier remedy for expired tokens: leads with "Go to Secrets" instead of burying it after a shell command. Closes jonwiggins#435 Co-authored-by: Optio Agent <optio-agent@noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…onwiggins#442) The backend already supports ticket triggers (type="ticket" with config={source, labels?}), but no UI existed to create them. This adds the "Ticket" option to the TriggerSelector component with a source dropdown (GitHub, Linear, Jira, Notion) and an optional labels array. - Add ticket type, source dropdown, and labels input to TriggerSelector - Wire ticket trigger config through /tasks/new form submission - Wire ticket trigger config through /tasks/scheduled/:id detail page - Add Ticket icon to trigger displays in list and detail pages - Add "ticket" to createTaskConfigTrigger type in api-client - Update scheduled-tasks docs guide to mention ticket triggers - Add comprehensive tests for the new ticket trigger UI Co-authored-by: Optio Agent <optio-agent@noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…out PR (jonwiggins#443) A Repo Task that completes successfully without opening a PR now transitions to needs_attention instead of completed. This prevents false-positive success badges when the agent exits cleanly but the work never shipped. The fix adds a post-agent check: if the task has a repoUrl, is not a review/planning task, and no PR URL was detected in logs, the system first queries the Git provider API as a fallback. If a PR is found there, the task transitions to pr_opened normally. Otherwise it transitions to needs_attention with a clear message so the user can resume or restart the agent. Also adds a "Completed without PR" label in the UI attention reason formatter, and state machine lifecycle tests for the escalation path. Closes jonwiggins#433 Co-authored-by: Optio Agent <optio-agent@noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…iggins#441) Empty-string environment variables (e.g. from Helm rendering a missing value as '') caused parseInt("", 10) to return NaN, bypassing the ?? nullish-coalescing default. This crashed the API pod on startup when OPTIO_REPO_INIT_TIMEOUT_MS was set to empty string. Introduces a shared parseIntEnv() utility that treats empty/whitespace- only strings the same as undefined, falling back to the default value. Applied to all 19 parseInt(process.env.X ?? "default", 10) call sites. Co-authored-by: Optio Agent <optio-agent@noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
`helm upgrade --reuse-values` silently ignores chart defaults added
after the original install, so any new values.yaml key renders as an
empty string in downstream templates. This bit us when
`agent.repoInitTimeoutMs` was added: the secret key rendered empty,
`parseInt("")` returned `NaN`, and the API crashlooped on the new
positive-integer validation in repo-pool-service.ts.
Switch to `--reset-then-reuse-values` so fresh chart defaults merge on
top of the stored release values (encryption.key etc. are preserved).
Introduces a per-run reconciler shaped after Kubernetes controllers:
event sources enqueue {kind, id} keys; a worker pops each, builds a
frozen WorldSnapshot, runs a pure decision function, and applies the
resulting Action via a CAS-gated executor. A periodic resync sweeps
all non-terminal runs as a correctness backstop for lost events.
- Decision layer (packages/shared/src/reconcile): reconcileRepo (10
states) and reconcileStandalone (4 states) return a typed Action
union (transition, patchStatus, deferWithBackoff, launchReview,
autoMergePr, resumeAgent, etc.). Fully pure; no DB, no clock.
- Executor (apps/api/src/services/reconcile-executor.ts): CAS-gates
every mutation on updated_at; delegates repo transitions to the
existing transitionTask so event/webhook fan-out is preserved;
defers side-effect actions in this phase until Phase C wires event
sources.
- Snapshot builder (reconcile-snapshot.ts): reads run row, pod state,
PR status, deps, capacity, heartbeat, and repo settings in parallel,
records read failures for defer-with-backoff.
- Workers (reconcile-worker.ts, reconcile-resync-worker): BullMQ queue
keyed by \`\${kind}__\${id}\` for per-key dedup; 5-min resync interval.
- Schema: adds control_intent, reconcile_backoff_until,
reconcile_attempts to both tasks and workflow_runs. Nullable /
default 0 — no backfill needed.
Feature-flagged: off by default. Enable with OPTIO_RECONCILE_ENABLED=true;
shadow mode on by default (OPTIO_RECONCILE_SHADOW=true).
Tests: 128 new (73 pure decisions + 33 edge cases + 22 executor).
Claude CLIs catch 401s internally and exit 0, which previously caused
runs that hit "Invalid authentication credentials" mid-execution to be
marked state=completed with no errorMessage. The UI banner and auth
detector correctly flagged the token as expired, but individual runs
looked like successes.
- detectAuthFailureInLogs(logs) in auth-failure-detector.ts: pure
scanner over the accumulated agent log blob using the existing
AUTH_FAILURE_PATTERNS ("api error: 401", "authentication_error",
'"status":401', etc.). Returns the first matching pattern and a
whitespace-normalized excerpt capped at 240 chars.
- workflow-worker and task-worker now run this check right after
adapter.parseResult. On a match, the result is coerced to
success=false with a classified errorMessage, and recordAuthEvent
fires so the global banner surfaces it.
- scripts/update-claude-auth.sh: one-shot refresh of the stored
CLAUDE_CODE_OAUTH_TOKEN. On macOS, extracts the token from the
"Claude Code-credentials" Keychain entry (same path the UI banner
uses) and POSTs it to /api/secrets for encryption + validation
against Anthropic. Falls back to manual paste on Linux or if the
Keychain entry is missing.
Tests: 10 new cases on detectAuthFailureInLogs covering each pattern,
case insensitivity, excerpt shape, first-match precedence, and
unrelated-"401" false-positive rejection.
Reorder the overview panel so sections appear as: 1. Recent Tasks (first) 2. Pods 3. Recent Events 4. Recent Activity (last) Previously Recent Activity appeared before Pods/Recent Events. Each section now renders as its own full-width block instead of being paired in a 2-column grid. Closes jonwiggins#451 Co-authored-by: Optio Agent <optio-agent@noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…#453) Add a GenericOIDCProvider that supports any OpenID Connect-compatible identity provider (Keycloak, Authentik, Authelia, Zitadel, Okta, Auth0, etc.) via standard discovery protocol. - New provider class in apps/api/src/services/oauth/oidc.ts with: - Auto-discovery via .well-known/openid-configuration (24h cache) - ID token signature verification using JWKS (jose library) - OIDC claim mapping (sub, email, name/preferred_username, picture) - Conditional registration when OIDC_ISSUER_URL is set - Optional prepare() hook on OAuthProvider interface for async init - Helm chart values (auth.oidc.*) and secrets template wiring - KeyRound icon fallback for unknown providers on login page - Docs, .env.example, and CLAUDE.md updated - 19 unit tests covering discovery, caching, token exchange, signature verification, tampered token rejection, and registration Closes jonwiggins#448 Co-authored-by: Optio Agent <optio-agent@noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restore the 2-column layout so Recent Tasks sits on the left and Pods on the right, instead of each rendering as full-width blocks.
Include Recent Activity in the same grid as Recent Tasks and Pods so all three reflow together: 1 column on mobile, 2 on md, 3 on xl.
Each column is at least 28rem wide before stacking. The grid now naturally flows from 1 to 3 columns depending on available width instead of switching at fixed breakpoints that left columns cramped.
Switch the overview section container from CSS grid (which stretched rows to the tallest item and left a gap before Recent Activity wrapped onto a new row) to a multi-column layout. Items now stack tightly within each column and flow between columns as width allows. Also promote the "Recent Events" subheading inside PodsList to match the h2 style used by Recent Tasks, Pods, and Recent Activity so all four section titles render identically.
Add docs/reconciliation.md covering the K8s-style control plane (decision flow, what it reconciles, triggers, env vars, schema, debugging) and docs/tasks.md covering the two task flavors, the three internal types, and the unified /api/tasks layer. Surface both in the README and add a reconciliation entry to CLAUDE.md's Key Subsystems.
Workflow logs live in workflow_run_logs, not task_logs, so the auth detector never saw 401s from Standalone Task runs and the banner wouldn't trigger. Add an auth_events check for tokenType=claude that mirrors the existing github path — the workflow worker already records claude auth_events on detection, the detector just wasn't reading them.
…ask pages Repo Task and Standalone Task pages each reimplemented the same UI primitives. Unify on the repo-task versions where they existed. - TokenRefreshBanner gains the numbered-step layout (1. Copy / 2. Paste) from the repo task page; the repo task page now imports it instead of inlining the form. Standalone run page and overview panel pick up the nicer layout for free. - Delete the two local RunStateBadge / RUN_STATE_CONFIG definitions in the jobs pages; use the shared StateBadge already powering task-card. - Extract MetadataCard into its own component with a sm/lg size prop so both the workflow detail stats bar and the run detail runtime cards can share it.
…th token (jonwiggins#455) When getClaudeUsage() receives a 401 or 403 from Anthropic's usage API, it now records an auth event and publishes an auth:failed WebSocket event so the UI shows the re-auth banner immediately — without waiting for the next 5-minute /api/auth/status poll or a task launch failure. Closes jonwiggins#454 Co-authored-by: Optio Agent <optio-agent@noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…input totals (jonwiggins#457) All agent adapters were only summing input_tokens and output_tokens from Anthropic's usage object, ignoring cache_creation_input_tokens and cache_read_input_tokens. Since agent loops are heavily cache-driven, this caused input token counts to be undercounted by 10-100x, making cost analytics and per-task spend attribution misleading. Add cache_creation_input_tokens and cache_read_input_tokens to the input token accumulation in all six adapters: claude-code, openclaw, opencode, gemini, copilot, and codex. Add corresponding unit tests to prevent regression. Co-authored-by: Optio Agent <optio-agent@noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removes shadow mode and the env-flag gate. The reconciler now owns PR-driven transitions, auto-merge, auto-resume, review launch, stall and pod-death detection, control-intent handling, and standalone-run auto-retry with exponential backoff. Workers stay as side-effect doers but no longer drive state transitions in parallel. Executor now implements the five previously-deferred side-effect actions (requeueForAgent, enqueueAgent, resumeAgent, launchReview, autoMergePr) and publishes events + webhooks on standalone transitions. transitionTask and workflow-worker's transitionRun fire enqueueReconcile after every successful transition, so any state change wakes the reconciler within milliseconds. pr-watcher slimmed from ~600 to ~290 lines: it now only polls PRs, writes the refreshed fields to the row, and enqueues a reconcile — all decision logic flows through reconcile-repo.ts. repo-cleanup keeps its stale-task recovery loop but stops firing the FAILED transition itself on pod death; the reconciler observes pod.phase=error from the snapshot and owns the transition. Snapshot now loads pod state for standalone runs and computes recentAutoResumeCount so decideFromPrStatus can cap the auto-resume loop. New decideFailed for standalone runs returns a transition-with- backoff that the executor pairs with a delayed reconcile job. Bugfixes uncovered during cutover: - enqueueReconcile used a stable jobId, which caused BullMQ to silently drop re-enqueues for any task whose prior reconcile sat in the completed set. Switched to unique jobIds per enqueue. - requeueForAgent and enqueueAgent now use jobId=runId so process-task jobs dedup against the original enqueue, preventing the race that caused InvalidTransitionError cascades. - tryTransitionTask catches InvalidTransitionError as a lost-race signal in addition to StateRaceError. - detectAuthFailureInLogs no longer scans inside `user`/`assistant` NDJSON events, fixing the false positive when an agent reads a test fixture containing literal pattern text. - Task detail page no longer renders the error panel for pr_opened state; a non-fatal warning shouldn't paint a healthy task as failed. TODO(reconciler) left in repo-cleanup-worker for the stall-recovery loop, which has retry-cap and orphan-agent-killing semantics that need lifting into the snapshot/decision/executor before it can move. See docs/reconciliation.md for the updated control-plane diagram.
Stable jobIds were being silently no-op'd by BullMQ when a prior process-task or process-workflow-run job for the same id sat in the completed/failed set. This blocked auto-retried workflow runs from ever progressing past the second enqueue: decideFailed transitioned them to QUEUED, decideQueued returned enqueueAgent on every pass, and the executor's add() was a no-op every time. Same root cause as the enqueueReconcile dedup fix in the cutover commit. The worker's existing "skip if state != queued" defensive check (task-worker.ts:106, workflow-worker.ts:276) protects against duplicate concurrent claims, so unique jobIds are safe.
Both the auto-retry path (decideFailed) and the user-driven retry path (interpretIntent retry) transitioned a FAILED run back to QUEUED with errorMessage cleared and retryCount incremented, but left finishedAt holding the original failure timestamp. When the worker subsequently transitioned QUEUED→RUNNING, the reconcile wake-up landed in decideRunning, which treats `finishedAt && !errorMessage` as "agent already finished successfully" and short-circuits to COMPLETED. The retry was marked completed without the agent ever running again (observed: started_at = today, finished_at = days ago, output null). Including `finishedAt: null` in both retry statusPatches keeps the "agent already finished" detection on real finished runs only.
…caling Standalone runs previously spawned one Kubernetes pod each — a burst of triggers would open tens of pods even though only a few ran at once. Pool pods per-workflow instead, mirroring repo pod scaling: each workflow has `maxPodInstances` replicas (default 1, max 20) hosting up to `maxAgentsPerPod` concurrent runs (default 2, max 50). Runs inject env per-exec and track their assigned pod via `workflow_runs.pod_id`, with `last_pod_id` preserving retry affinity. Selection order matches repo pods: preferred pod → least-loaded → scale-up → overflow wait.
…et (jonwiggins#460) GitHub ticket sync threw "provider requires token" whenever no provider-specific secret was stored, even though a GitHub App was configured at the server level. Now routes through the existing getGitHubToken({ server: true }) resolver so the App installation token (or PAT) is used when the provider config/secret omits one. Closes jonwiggins#458
- Remove gynzy's custom schedules feature (schedules table, routes, worker, UI) - Adopt main's unified task model (task_configs, workflow_triggers, reconciler) - Fix workflow-pool-service tests for renamed getOrCreateWorkflowPod function - Update mock to include parseJsonEnv for nodeSelector/tolerations tests - Accept main's UID 1001 for agent pods (matches base.Dockerfile) - Keep nodeSelector/tolerations env var support as gynzy feature
The securityContext was incorrectly set to UID 1001, but the agent user in images/base.Dockerfile is created with UID 1000. This mismatch caused git config --global to fail with "Permission denied" because HOME resolved to / when running as a non-existent UID.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Changes
Testing
pnpm turbo test)pnpm turbo typecheck)Related
Closes #
Screenshots