From 1124fcbf2a21451310428f056c056142ea262e40 Mon Sep 17 00:00:00 2001 From: Zbigniew Sobiecki Date: Fri, 2 Jan 2026 14:34:36 +0100 Subject: [PATCH 1/6] feat: add Redis configuration and startup for agents (#13) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Redis support alongside PostgreSQL, enabling agents to use in-memory data storage for caching, queues, and session management. Changes: - Install redis-server and redis-tools in Docker image - Configure Redis with AOF persistence, 256MB memory limit, and LRU eviction - Add startRedis() function following the same pattern as startPostgres() - Integrate Redis startup into agent lifecycle (base.ts, review.ts) - Document Redis CLI commands and usage in agent prompts - Both PostgreSQL and Redis are critical: agents fail if either service doesn't start Redis configuration: - Port: 6379 (standard) - Bind: localhost only for security - Persistence: AOF with everysec fsync - Memory: 256MB with allkeys-lru eviction policy - Access: via redis-cli through Tmux gadget 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.5 --- Dockerfile | 35 +++++++ src/agents/base.ts | 4 + .../templates/partials/environment.eta | 15 +++ src/agents/review.ts | 4 + src/agents/utils/index.ts | 1 + src/agents/utils/setup.ts | 94 +++++++++++++++++++ 6 files changed, 153 insertions(+) diff --git a/Dockerfile b/Dockerfile index 6c21fd45..af590192 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,6 +55,41 @@ RUN mkdir -p /run/postgresql && chown -R postgres:postgres /run/postgresql \ && su postgres -c "psql -c \"ALTER USER postgres WITH PASSWORD 'postgres';\"" \ && su postgres -c "/usr/lib/postgresql/*/bin/pg_ctl stop -D /var/lib/postgresql/data" +# Install and configure Redis for local development use by agents +RUN apt-get update && apt-get install -y \ + redis-server \ + redis-tools \ + && rm -rf /var/lib/apt/lists/* + +# Configure Redis +# - Port: 6379 (standard) +# - Bind: localhost only for security +# - Persistence: AOF enabled +# - Memory: 256MB limit with LRU eviction +RUN mkdir -p /var/lib/redis /var/log/redis /var/run/redis && \ + chown -R redis:redis /var/lib/redis /var/log/redis /var/run/redis && \ + { \ + echo "# Redis Configuration for CASCADE Agents"; \ + echo "bind 127.0.0.1"; \ + echo "port 6379"; \ + echo "daemonize no"; \ + echo "supervised systemd"; \ + echo "pidfile /var/run/redis/redis-server.pid"; \ + echo "loglevel notice"; \ + echo "logfile /var/log/redis/redis-server.log"; \ + echo "dir /var/lib/redis"; \ + echo "# Persistence"; \ + echo "appendonly yes"; \ + echo "appendfilename \"appendonly.aof\""; \ + echo "appendfsync everysec"; \ + echo "# Memory Management"; \ + echo "maxmemory 256mb"; \ + echo "maxmemory-policy allkeys-lru"; \ + echo "# Disable protected mode for local dev"; \ + echo "protected-mode no"; \ + } > /etc/redis/redis.conf && \ + chown redis:redis /etc/redis/redis.conf + # Install ast-grep RUN ARCH=$(dpkg --print-architecture) && \ if [ "$ARCH" = "amd64" ]; then AST_ARCH="x86_64"; else AST_ARCH="aarch64"; fi && \ diff --git a/src/agents/base.ts b/src/agents/base.ts index bad7595d..8e97305b 100644 --- a/src/agents/base.ts +++ b/src/agents/base.ts @@ -30,6 +30,7 @@ import { installDependencies, readContextFiles, startPostgres, + startRedis, warmTypeScriptCache, } from './utils/index.js'; import { createAgentLogger } from './utils/logging.js'; @@ -63,6 +64,9 @@ async function setupRepository( // Start PostgreSQL if available (for local database testing) await startPostgres(); + // Start Redis if available (for caching, queues, session storage) + await startRedis(); + // Clone repo to temp directory const repoDir = createTempDir(project.id); cloneRepo(project, repoDir); diff --git a/src/agents/prompts/templates/partials/environment.eta b/src/agents/prompts/templates/partials/environment.eta index 20ac381b..4533bcc5 100644 --- a/src/agents/prompts/templates/partials/environment.eta +++ b/src/agents/prompts/templates/partials/environment.eta @@ -7,6 +7,21 @@ - **Stop**: `su postgres -c 'pg_ctl stop -D /var/lib/postgresql/data'` - **Status**: `su postgres -c 'pg_ctl status -D /var/lib/postgresql/data'` - **Create database**: `psql -U postgres -h localhost -c 'CREATE DATABASE mydb;'` +- **Redis**: In-memory data store running on port 6379 + - **Connection**: `redis://localhost:6379` (no password) + - **Connect via CLI**: `redis-cli` + - **Common commands**: + - `redis-cli ping` - Check if Redis is running + - `redis-cli SET key value` - Set a key + - `redis-cli GET key` - Get a key's value + - `redis-cli KEYS "*"` - List all keys (use sparingly) + - `redis-cli FLUSHALL` - Clear all data (use with caution) + - `redis-cli INFO` - Get server statistics + - **Start**: `su redis -c 'redis-server /etc/redis/redis.conf --daemonize yes'` + - **Stop**: `redis-cli SHUTDOWN` + - **Monitor**: `redis-cli MONITOR` - Watch all commands in real-time + - **Data persistence**: AOF enabled (data survives restarts) + - **Memory limit**: 256MB with LRU eviction - **Node.js 22**: With npm, pnpm, yarn, bun - **Git & GitHub CLI**: `git` and `gh` commands available - **Search tools** (use via Tmux for fast codebase exploration): diff --git a/src/agents/review.ts b/src/agents/review.ts index 2d9215e4..7fa8dd04 100644 --- a/src/agents/review.ts +++ b/src/agents/review.ts @@ -25,6 +25,7 @@ import { installDependencies, readContextFiles, startPostgres, + startRedis, } from './utils/index.js'; import { createAgentLogger } from './utils/logging.js'; @@ -57,6 +58,9 @@ async function setupRepository( // Start PostgreSQL if available (for local database testing) await startPostgres(); + // Start Redis if available (for caching, queues, session storage) + await startRedis(); + // Clone repo to temp directory const repoDir = createTempDir(project.id); cloneRepo(project, repoDir); diff --git a/src/agents/utils/index.ts b/src/agents/utils/index.ts index be42c02d..7724f729 100644 --- a/src/agents/utils/index.ts +++ b/src/agents/utils/index.ts @@ -2,6 +2,7 @@ export { LOG_LEVELS, getLogLevel, startPostgres, + startRedis, generateDirectoryListing, type ContextFile, readContextFiles, diff --git a/src/agents/utils/setup.ts b/src/agents/utils/setup.ts index 52db41bf..1331c7eb 100644 --- a/src/agents/utils/setup.ts +++ b/src/agents/utils/setup.ts @@ -95,6 +95,100 @@ export async function startPostgres(): Promise { logger.info('PostgreSQL started successfully'); } +// ============================================================================ +// Redis Startup +// ============================================================================ + +let redisStarted = false; + +export async function startRedis(): Promise { + if (redisStarted) return; + + const REDIS_CONF = '/etc/redis/redis.conf'; + const REDIS_LOG = '/var/log/redis/redis-server.log'; + + try { + // Check if Redis is already running + const statusResult = await execCommand('redis-cli', ['ping'], '/'); + + if (statusResult.stdout.trim() === 'PONG') { + logger.info('Redis already running'); + redisStarted = true; + return; + } + } catch { + // Not running, continue to start it + } + + // Start Redis as redis user + logger.info('Starting Redis...'); + + // Ensure runtime directories exist (may not persist across container restarts) + try { + await execCommand('mkdir', ['-p', '/var/run/redis'], '/'); + await execCommand('chown', ['redis:redis', '/var/run/redis'], '/'); + await execCommand('mkdir', ['-p', '/var/lib/redis'], '/'); + await execCommand('chown', ['redis:redis', '/var/lib/redis'], '/'); + await execCommand('mkdir', ['-p', '/var/log/redis'], '/'); + await execCommand('chown', ['redis:redis', '/var/log/redis'], '/'); + } catch (err) { + logger.warn('Failed to create Redis directories', { error: String(err) }); + } + + try { + // Start Redis in background using redis-server + // We use su to run as redis user, and redirect stderr to capture startup errors + const startResult = await execCommand( + 'su', + ['redis', '-c', `redis-server ${REDIS_CONF} --daemonize yes 2>&1`], + '/', + ); + logger.debug('redis-server start output', { + stdout: startResult.stdout, + stderr: startResult.stderr, + }); + } catch (err) { + // Try to read the log file for more details + try { + const logResult = await execCommand('cat', [REDIS_LOG], '/'); + logger.error('Redis log contents', { log: logResult.stdout }); + } catch { + // Ignore if log file doesn't exist + } + logger.error('Failed to start Redis', { error: String(err) }); + throw new Error(`Redis failed to start. Check ${REDIS_LOG} for details. Error: ${err}`); + } + + // Wait a moment for Redis to initialize + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Verify it's actually running + try { + const verifyResult = await execCommand('redis-cli', ['ping'], '/'); + logger.debug('redis-cli ping output', { + stdout: verifyResult.stdout, + stderr: verifyResult.stderr, + }); + + if (verifyResult.stdout.trim() !== 'PONG') { + // Try to read the log file for more details + try { + const logResult = await execCommand('cat', [REDIS_LOG], '/'); + logger.error('Redis log contents', { log: logResult.stdout }); + } catch { + // Ignore if log file doesn't exist + } + throw new Error(`Redis ping check failed. Output: ${verifyResult.stdout}`); + } + } catch (err) { + logger.error('Redis ping check failed after start', { error: String(err) }); + throw new Error(`Redis failed to start properly: ${err}`); + } + + redisStarted = true; + logger.info('Redis started successfully'); +} + // ============================================================================ // Log Level Configuration // ============================================================================ From b2d274e01ade1c178dc9ef27b5e07c2355325459 Mon Sep 17 00:00:00 2001 From: zbigniew sobiecki Date: Fri, 2 Jan 2026 14:45:03 +0100 Subject: [PATCH 2/6] fix: increase post-job grace period to allow debug agent webhooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The debug agent wasn't triggering because CASCADE was shutting down too quickly after uploading session logs. Trello webhooks for attachment uploads take a few seconds to arrive, but we were only waiting 5 seconds before shutting down the machine. Increased postJobGracePeriodMs from 5s to 45s to ensure: 1. Agent uploads session log attachment 2. Trello webhook is delivered while machine is still running 3. Debug agent trigger processes the webhook 4. Debug agent runs before shutdown This fixes the race condition where attachment webhooks arrived after the machine had already shut down. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- config/projects.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/projects.json b/config/projects.json index 184e6332..2ba5691c 100644 --- a/config/projects.json +++ b/config/projects.json @@ -5,7 +5,8 @@ "agentIterations": { "implementation": 65 }, - "selfDestructTimeoutMs": 1800000 + "selfDestructTimeoutMs": 1800000, + "postJobGracePeriodMs": 45000 }, "projects": [ { From b1809e2fa1b55523e93e899ba5daad2a8eeaa8cf Mon Sep 17 00:00:00 2001 From: zbigniew sobiecki Date: Fri, 2 Jan 2026 14:49:09 +0100 Subject: [PATCH 3/6] feat: add support for GitHub PR review submission webhooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both pull_request_review (review submission) and pull_request_review_comment (review comments) now trigger the review agent for PRs with Trello card URLs. Changes: - Updated webhook queue to store event types alongside payloads - Added PRReviewSubmittedTrigger for review submissions (approve/request changes) - Fixed hardcoded event type in GitHub webhook dequeuing - Both review types now trigger the review agent when PR has Trello card URL Previously, only review comments triggered the agent. Now review submissions (when someone clicks 'Approve' or 'Request Changes') also trigger it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/triggers/github/index.ts | 1 + src/triggers/github/pr-review-submitted.ts | 61 ++++++++++++++++++++++ src/triggers/github/webhook-handler.ts | 21 +++++--- src/triggers/index.ts | 4 ++ src/utils/webhookQueue.ts | 4 +- tmp-test.sh | 9 ++++ 6 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 src/triggers/github/pr-review-submitted.ts create mode 100755 tmp-test.sh diff --git a/src/triggers/github/index.ts b/src/triggers/github/index.ts index acf6eb91..152967f2 100644 --- a/src/triggers/github/index.ts +++ b/src/triggers/github/index.ts @@ -1,6 +1,7 @@ export { CheckSuiteFailureTrigger } from './check-suite-failure.js'; export { PRReadyToMergeTrigger } from './pr-ready-to-merge.js'; export { PRReviewCommentTrigger } from './pr-review-comment.js'; +export { PRReviewSubmittedTrigger } from './pr-review-submitted.js'; export { processGitHubWebhook } from './webhook-handler.js'; export * from './types.js'; export * from './utils.js'; diff --git a/src/triggers/github/pr-review-submitted.ts b/src/triggers/github/pr-review-submitted.ts new file mode 100644 index 00000000..79979b37 --- /dev/null +++ b/src/triggers/github/pr-review-submitted.ts @@ -0,0 +1,61 @@ +import type { TriggerContext, TriggerHandler, TriggerResult } from '../../types/index.js'; +import { logger } from '../../utils/logging.js'; +import { isGitHubPullRequestReviewPayload } from './types.js'; +import { extractTrelloCardId, hasTrelloCardUrl } from './utils.js'; + +export class PRReviewSubmittedTrigger implements TriggerHandler { + name = 'pr-review-submitted'; + description = 'Triggers review agent when a PR review is submitted'; + + matches(ctx: TriggerContext): boolean { + if (ctx.source !== 'github') return false; + if (!isGitHubPullRequestReviewPayload(ctx.payload)) return false; + + // Only trigger on submitted reviews, not edits or dismissals + return ctx.payload.action === 'submitted'; + } + + async handle(ctx: TriggerContext): Promise { + // Type assertion since we validated in matches() + const reviewPayload = ctx.payload as { + pull_request: { number: number; body: string | null; head: { ref: string } }; + repository: { full_name: string }; + review: { id: number; body: string | null; html_url: string; state: string }; + }; + + const prNumber = reviewPayload.pull_request.number; + + // Check if PR has Trello card URL in body + const prBody = reviewPayload.pull_request.body || ''; + if (!hasTrelloCardUrl(prBody)) { + logger.info('PR does not have Trello card URL, skipping review submission trigger', { + prNumber, + reviewState: reviewPayload.review.state, + }); + return null; + } + + const cardId = extractTrelloCardId(prBody); + + logger.info('PR review submitted, triggering review agent', { + prNumber, + reviewState: reviewPayload.review.state, + cardId, + }); + + return { + agentType: 'review', + agentInput: { + prNumber, + prBranch: reviewPayload.pull_request.head.ref, + repoFullName: reviewPayload.repository.full_name, + triggerCommentId: reviewPayload.review.id, + triggerCommentBody: reviewPayload.review.body || `Review: ${reviewPayload.review.state}`, + triggerCommentPath: '', // Reviews don't have a specific file path + triggerCommentUrl: reviewPayload.review.html_url, + }, + prNumber, + cardId: cardId || undefined, + }; + } +} diff --git a/src/triggers/github/webhook-handler.ts b/src/triggers/github/webhook-handler.ts index a7813d54..2f491060 100644 --- a/src/triggers/github/webhook-handler.ts +++ b/src/triggers/github/webhook-handler.ts @@ -74,13 +74,15 @@ async function executeGitHubAgent( function processNextQueuedGitHubWebhook(config: CascadeConfig, registry: TriggerRegistry): void { const next = dequeueWebhook(); if (next) { - logger.info('Processing queued GitHub webhook', { queueLength: getQueueLength() }); + const eventType = next.eventType || 'pull_request_review_comment'; // Fallback for backward compatibility + logger.info('Processing queued GitHub webhook', { + queueLength: getQueueLength(), + eventType, + }); setImmediate(() => { - processGitHubWebhook(next.payload, 'pull_request_review_comment', config, registry).catch( - (err) => { - logger.error('Failed to process queued GitHub webhook', { error: String(err) }); - }, - ); + processGitHubWebhook(next.payload, eventType, config, registry).catch((err) => { + logger.error('Failed to process queued GitHub webhook', { error: String(err) }); + }); }); } else if (process.env.FLY_APP_NAME) { scheduleShutdownAfterJob(config.defaults.postJobGracePeriodMs); @@ -106,9 +108,12 @@ export async function processGitHubWebhook( } if (isCurrentlyProcessing()) { - const queued = enqueueWebhook(payload); + const queued = enqueueWebhook(payload, eventType); if (queued) { - logger.info('Currently processing, GitHub webhook queued', { queueLength: getQueueLength() }); + logger.info('Currently processing, GitHub webhook queued', { + queueLength: getQueueLength(), + eventType, + }); } else { logger.warn('Queue full, GitHub webhook rejected', { queueLength: getQueueLength() }); } diff --git a/src/triggers/index.ts b/src/triggers/index.ts index 8fb4d313..77090a6d 100644 --- a/src/triggers/index.ts +++ b/src/triggers/index.ts @@ -1,6 +1,7 @@ import { CheckSuiteFailureTrigger } from './github/check-suite-failure.js'; import { PRReadyToMergeTrigger } from './github/pr-ready-to-merge.js'; import { PRReviewCommentTrigger } from './github/pr-review-comment.js'; +import { PRReviewSubmittedTrigger } from './github/pr-review-submitted.js'; import type { TriggerRegistry } from './registry.js'; import { AttachmentAddedTrigger } from './trello/attachment-added.js'; import { @@ -36,6 +37,9 @@ export function registerBuiltInTriggers(registry: TriggerRegistry): void { // GitHub: PR review comment trigger registry.register(new PRReviewCommentTrigger()); + // GitHub: PR review submission trigger (when someone submits a review) + registry.register(new PRReviewSubmittedTrigger()); + // GitHub: Check suite failure trigger (runs review agent to fix) registry.register(new CheckSuiteFailureTrigger()); diff --git a/src/utils/webhookQueue.ts b/src/utils/webhookQueue.ts index e5cadb05..9f2b546a 100644 --- a/src/utils/webhookQueue.ts +++ b/src/utils/webhookQueue.ts @@ -4,12 +4,13 @@ const MAX_QUEUE_SIZE = 10; interface QueuedWebhook { payload: unknown; + eventType?: string; // Optional for backward compatibility (Trello doesn't need it) receivedAt: Date; } const queue: QueuedWebhook[] = []; -export function enqueueWebhook(payload: unknown): boolean { +export function enqueueWebhook(payload: unknown, eventType?: string): boolean { if (queue.length >= MAX_QUEUE_SIZE) { logger.warn('Webhook queue full, rejecting', { queueLength: queue.length, @@ -20,6 +21,7 @@ export function enqueueWebhook(payload: unknown): boolean { queue.push({ payload, + eventType, receivedAt: new Date(), }); diff --git a/tmp-test.sh b/tmp-test.sh new file mode 100755 index 00000000..6f57262d --- /dev/null +++ b/tmp-test.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# docker build -t cascade . && docker run -it cascade /tmp/tmp-test.sh + +mkdir /tmp/p +cd /tmp/p +env GH_TOKEN=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 gh repo clone https://github.com/zbigniewsobiecki/niu.git +cd niu +pnpm install From 61c84eb322df8270d9b2ce99f143a066d00e5f0f Mon Sep 17 00:00:00 2001 From: zbigniew sobiecki Date: Fri, 2 Jan 2026 14:52:20 +0100 Subject: [PATCH 4/6] fix: move cards to IN REVIEW after PR creation from GitHub webhooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds automatic card movement logic to executeGitHubAgent that mirrors the existing behavior in Trello webhook handler. When implementation agent creates a PR, the card is now moved to the IN REVIEW list and a comment with the PR URL is added. This ensures consistent behavior regardless of whether the agent was triggered by a Trello webhook or a GitHub webhook. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/triggers/github/webhook-handler.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/triggers/github/webhook-handler.ts b/src/triggers/github/webhook-handler.ts index 2f491060..c6d8a3db 100644 --- a/src/triggers/github/webhook-handler.ts +++ b/src/triggers/github/webhook-handler.ts @@ -63,6 +63,18 @@ async function executeGitHubAgent( ); } + // Move to in-review if implementation and PR was created + if (cardId && result.agentType === 'implementation' && agentResult.prUrl) { + await safeOperation(() => trelloClient.moveCardToList(cardId, project.trello.lists.inReview), { + action: 'move card to in-review', + cardId, + }); + await safeOperation(() => trelloClient.addComment(cardId, `PR created: ${agentResult.prUrl}`), { + action: 'add PR comment', + cardId, + }); + } + logger.info('GitHub agent completed', { agentType: result.agentType, prNumber: result.prNumber, From 0fede46a922dff3348f79cf1e9600750923ceb71 Mon Sep 17 00:00:00 2001 From: zbigniew sobiecki Date: Fri, 2 Jan 2026 14:54:52 +0100 Subject: [PATCH 5/6] docs: add GitHub webhook integration documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds comprehensive documentation for GitHub webhook setup: - GitHub webhook endpoint in API endpoints section - Detailed setup instructions with configuration steps - List of supported GitHub triggers and their behavior - Updated features list to include GitHub integration This complements the recent GitHub webhook improvements including PR review submission support and automatic card movement. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b7024add..3999ba1c 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,10 @@ Multi-project Trello-to-code automation platform. CASCADE reacts to Trello card - **Multi-project support** - Single deployment handles multiple repos/Trello boards - **Extensible trigger system** - Easy to add new triggers (card moved, label added, PR ready, etc.) -- **AI-powered agents** - Briefing, planning, and implementation agents using llmist +- **AI-powered agents** - Briefing, planning, implementation, review, and debug agents using llmist - **Git workflow** - Automatic branch creation, commits, and PR creation - **Trello integration** - Full card management (labels, comments, attachments) +- **GitHub integration** - PR review webhooks, automatic card movement, CI check monitoring ## Getting Started @@ -270,6 +271,30 @@ curl -X POST "https://api.trello.com/1/webhooks" \ -d "description=Cascade webhook" ``` +### GitHub Webhook Setup + +Set up GitHub webhooks for your repository to enable PR review triggers: + +1. Go to your repository settings: `https://github.com/owner/repo/settings/hooks` +2. Click "Add webhook" +3. Configure: + - **Payload URL**: `https://cascade.fly.dev/github/webhook` + - **Content type**: `application/json` + - **Secret**: (optional, not currently validated) + - **Events**: Select individual events: + - Pull request review comments + - Pull request reviews + - Check suites +4. Click "Add webhook" + +**Supported GitHub Triggers**: +- **PR Review Comments**: Triggers review agent when someone comments on a PR review +- **PR Review Submissions**: Triggers review agent when someone submits a PR review (approve/request changes) +- **Check Suite Failures**: Triggers review agent to fix failed CI checks +- **PR Ready to Merge**: Auto-moves card to DONE when all checks pass and PR is approved + +**Note**: GitHub webhooks only trigger for PRs that have a Trello card URL in their description. + ## API Endpoints | Endpoint | Method | Description | @@ -278,6 +303,8 @@ curl -X POST "https://api.trello.com/1/webhooks" \ | `/health` | HEAD | Health check (no body) | | `/trello/webhooks` | POST | Trello webhook receiver | | `/trello/webhooks` | HEAD | Trello webhook verification | +| `/github/webhook` | POST | GitHub webhook receiver | +| `/github/webhook` | GET | GitHub webhook verification | ## License From d300ed34a4aeac286454db5bfe079678a913a6f2 Mon Sep 17 00:00:00 2001 From: Zbigniew Sobiecki Date: Fri, 2 Jan 2026 15:34:13 +0100 Subject: [PATCH 6/6] fix: redis startup fails due to nologin shell (#15) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The redis user has /usr/sbin/nologin as its default shell, which prevents `su redis -c` from working. This was causing Redis to fail to start in production with "This account is currently not available." Fix by using `su -s /bin/sh` to explicitly specify a shell for the redis user when starting the service. Also updated agent environment documentation to reflect the correct startup command. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.5 --- src/agents/prompts/templates/partials/environment.eta | 2 +- src/agents/utils/setup.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/agents/prompts/templates/partials/environment.eta b/src/agents/prompts/templates/partials/environment.eta index 4533bcc5..632526c2 100644 --- a/src/agents/prompts/templates/partials/environment.eta +++ b/src/agents/prompts/templates/partials/environment.eta @@ -17,7 +17,7 @@ - `redis-cli KEYS "*"` - List all keys (use sparingly) - `redis-cli FLUSHALL` - Clear all data (use with caution) - `redis-cli INFO` - Get server statistics - - **Start**: `su redis -c 'redis-server /etc/redis/redis.conf --daemonize yes'` + - **Start**: `su -s /bin/sh redis -c 'redis-server /etc/redis/redis.conf --daemonize yes'` - **Stop**: `redis-cli SHUTDOWN` - **Monitor**: `redis-cli MONITOR` - Watch all commands in real-time - **Data persistence**: AOF enabled (data survives restarts) diff --git a/src/agents/utils/setup.ts b/src/agents/utils/setup.ts index 1331c7eb..85f1b412 100644 --- a/src/agents/utils/setup.ts +++ b/src/agents/utils/setup.ts @@ -137,10 +137,10 @@ export async function startRedis(): Promise { try { // Start Redis in background using redis-server - // We use su to run as redis user, and redirect stderr to capture startup errors + // We use su -s /bin/sh to run as redis user (redis user has /usr/sbin/nologin shell by default) const startResult = await execCommand( 'su', - ['redis', '-c', `redis-server ${REDIS_CONF} --daemonize yes 2>&1`], + ['-s', '/bin/sh', 'redis', '-c', `redis-server ${REDIS_CONF} --daemonize yes 2>&1`], '/', ); logger.debug('redis-server start output', {