# ClickUp Runner Headless background service that polls ClickUp for tasks with the "Delegate to Claude" checkbox checked, routes them through a skill map based on task type + stage, runs Claude Code headless, and posts results back to ClickUp. ## Quick Start ```bash # Set required env vars (or put them in .env at repo root) export CLICKUP_API_TOKEN="pk_..." export CLICKUP_SPACE_ID="..." # Run the runner uv run python -m clickup_runner ``` ## How It Works 1. Every 720 seconds, polls all "Overall" lists in the ClickUp space 2. Checks for completed AutoCora jobs (result polling) 3. Finds tasks where: - "Delegate to Claude" checkbox is checked - Due date is today or earlier 4. Reads the task's Work Category and Stage fields 5. Looks up the skill route in `skill_map.py` 6. Dispatches to either: - **AutoCora handler** (for `run_cora` stage): submits a Cora job to the NAS queue - **Claude Code handler**: runs `claude -p` with the skill file + task context as prompt 7. On success: uploads output files as ClickUp attachments, copies to NAS (best-effort), advances Stage, sets next status, posts summary comment 8. On error: sets Error checkbox, posts structured error comment (what failed, how to fix) 9. Always unchecks "Delegate to Claude" after processing ## Configuration Config is loaded from `clickup_runner.yaml` at the repo root (optional), with env var overrides. ### clickup_runner.yaml ```yaml clickup: space_id: "your_space_id" task_type_field_name: "Work Category" delegate_field_name: "Delegate to Claude" stage_field_name: "Stage" error_field_name: "Error" ai_working_status: "ai working" review_status: "review" autocora: jobs_dir: "//PennQnap1/SHARE1/AutoCora/jobs" results_dir: "//PennQnap1/SHARE1/AutoCora/results" xlsx_dir: "//PennQnap1/SHARE1/Cora72-for-macro" poll_interval_seconds: 120 nas: generated_dir: "//PennQnap1/SHARE1/generated" runner: poll_interval_seconds: 720 claude_timeout_seconds: 2700 max_turns_default: 10 ``` ### Environment Variables | Variable | Required | Description | |----------|----------|-------------| | `CLICKUP_API_TOKEN` | Yes | ClickUp API token | | `CLICKUP_SPACE_ID` | Yes | ClickUp space to poll | | `NTFY_ERROR_TOPIC` | No | ntfy.sh topic for error notifications | | `NTFY_SUCCESS_TOPIC` | No | ntfy.sh topic for success notifications | ## ClickUp Custom Fields Required These must exist in your ClickUp space: | Field | Type | Purpose | |-------|------|---------| | Delegate to Claude | Checkbox | Trigger -- checked = process this task | | Stage | Dropdown | Pipeline position (run_cora, outline, draft, etc.) | | Error | Checkbox | Flagged when processing fails | | Work Category | Dropdown | Task type (Content Creation, Press Release, etc.) | | Keyword | Text | SEO keyword for Cora analysis (required for run_cora stage) | | IMSURL | URL | Target money-site URL (used in prompts and Cora jobs) | | Client | Dropdown | Client name (used for NAS file organization and prompts) | ## Skill Map The routing table lives in `skill_map.py`. Each task type has a sequence of stages, and each stage maps to either an AutoCora job or a Claude Code skill file. ### Content Creation ``` run_cora -> outline -> draft -> final ``` ### On Page Optimization ``` run_cora -> outline -> draft -> hidden div -> final ``` ### Press Release ``` draft -> final ``` ### Link Building ``` run_cora -> build -> final ``` ## Adding a New Task Type 1. Add an entry to `SKILL_MAP` in `skill_map.py` 2. Write the skill `.md` file(s) in `skills/` 3. Add the Work Category value in ClickUp 4. Add the Stage dropdown values in ClickUp ## Statuses | Status | Owner | Meaning | |--------|-------|---------| | To Do | Nobody | Not started | | In Progress | Human | Human is working on it | | Needs Input | Human | Blocked, needs info | | AI Working | Claude | Runner is processing | | Review | Human | Output ready for human review | | Client Review | Client | Sent to client | | Complete | Nobody | Done | ## Claude Code Handler When a task routes to a Claude handler, the runner: 1. Sets status to "AI Working" 2. Reads the skill `.md` file from `skills/` 3. Builds a prompt with skill instructions + task context: - Task name, description, customer, target URL - ClickUp task link - Attached `.xlsx` Cora report URLs (if any) - Instructions to write output files to the working directory 4. Runs `claude -p "" --allowedTools "..." --max-turns N --permission-mode bypassPermissions --bare` 5. Collects all files Claude created in the temp working directory 6. Uploads files to ClickUp as attachments 7. Copies files to NAS at `//PennQnap1/SHARE1/generated/{customer}/` (best-effort) 8. Advances Stage, updates status, posts comment, unchecks Delegate to Claude 9. Sends ntfy.sh notification (if configured) On failure, it posts a structured error comment: ``` [ERROR] Claude processing failed -- What failed: How to fix: ``` ## AutoCora Handler AutoCora jobs are asynchronous -- submission and result polling happen on separate poll cycles. ### Submission (when a `run_cora` task is found) 1. Reads the `Keyword` and `IMSURL` custom fields from the task 2. Sets status to "AI Working" 3. Writes a job JSON file to `//PennQnap1/SHARE1/AutoCora/jobs/`: ```json { "keyword": "CNC Machining", "url": "https://acme.com/cnc-machining", "task_ids": ["task_id"] } ``` 4. Stores job metadata in the state DB for result polling 5. Posts comment "Cora job submitted for keyword: ..." 6. Unchecks "Delegate to Claude" ### Result Polling (every poll cycle) At the start of each cycle, the runner scans the results directory: 1. Looks for `.result` files in `//PennQnap1/SHARE1/AutoCora/results/` 2. Matches results to pending jobs via the state DB 3. On **success**: - Advances Stage to the next stage (e.g. run_cora -> outline) - Sets status to "review" - Posts comment with keyword and .xlsx location - Clears Error checkbox - **Does NOT re-check Delegate to Claude** (human reviews first) 4. On **failure**: - Sets Error checkbox - Posts structured error comment with failure reason 5. Archives processed `.result` files to `results/processed/` ### .xlsx Skip If a task at `run_cora` stage already has an `.xlsx` attachment, the runner skips Cora submission and advances directly to the next stage. ## Logs - Console output: INFO level - File log: `logs/clickup_runner.log` (DEBUG level) - Run history: `data/clickup_runner.db` (run_log table + kv_store for AutoCora jobs) ## Troubleshooting ### Task not being picked up - Check that "Delegate to Claude" is checked - Check that the due date is today or earlier - Check that Work Category and Stage are set and valid - Check that the task is in an "Overall" list ### Claude errors - Check `logs/clickup_runner.log` for the full error - Verify the skill `.md` file exists in `skills/` - Verify `claude` CLI is on PATH - Check the Error comment on the ClickUp task for fix instructions ### AutoCora not producing results - Verify the NAS is mounted and accessible - Check that job files appear in `//PennQnap1/SHARE1/AutoCora/jobs/` - Check the AutoCora worker logs on the NAS - Look for `.result` files in `//PennQnap1/SHARE1/AutoCora/results/` ### NAS copy failures - NAS copy is best-effort and won't block the pipeline - Check that `//PennQnap1/SHARE1/generated/` is accessible - Check `logs/clickup_runner.log` for copy warnings ## Tests ```bash # Unit tests only (no credentials needed) uv run pytest tests/test_clickup_runner/ -m "not integration" # Full suite (needs CLICKUP_API_TOKEN) uv run pytest tests/test_clickup_runner/ # Specific test file uv run pytest tests/test_clickup_runner/test_autocora.py -v ```