A collection of PostHog skills for enhancing AI-assisted workflows. Add this repo as a Claude Code plugin marketplace to get access to all PostHog skills: Then install individual plugins: Or browse available plugins: Copy any skill directory to .claude/skills/ in your project: Any directory under skills/ that contains a .claude-plugin/plugin.json is automatically discovered and added to the market
Add this skill
npx mdskills install PostHog/debugging-signals-pipeline@PostHog? Sign in with GitHub to claim this listing.Comprehensive debugging guide with specific commands, API examples, and clear troubleshooting flows
1---2name: debugging-signals-pipeline3description: >4 Debug the signals pipeline locally end-to-end. Covers emitting test signals5 from fixtures, monitoring Temporal workflows via the REST API, reading sandbox6 agent logs from object storage, inspecting Docker sandbox containers, and7 diagnosing common failures (stale ClickHouse embeddings, agentsh network8 denials, inactivity timeouts). Use when a signal isn't reaching the inbox,9 a signal-report-summary workflow fails, or a sandbox task run times out.10---1112# Debugging the signals pipeline1314## Pipeline flow1516```text17emit_signals_from_fixture18 → signal-emitter (Temporal workflow)19 → buffer-signals (batches signals, 5s flush timer)20 → safety_filter_activity21 → flush_signals_to_s3_activity22 → signal_with_start_grouping_v2_activity23 → team-signal-grouping-v2 (30s batch collect window)24 → read_signals_from_s3_activity25 → get_embedding_activity + generate_search_queries_activity26 → run_signal_semantic_search_activity27 → match_signal_to_report_activity28 → assign_and_emit_signal_activity29 → wait_for_signal_in_clickhouse_activity30 → (if new report) signal-report-summary31 → fetch_signals_for_report_activity32 → report_safety_judge_activity33 → select_repository_activity (spawns Docker sandbox)34```3536## Emitting test signals3738```bash39# Emit a single signal from the Zendesk fixture at offset 2640DEBUG=1 python manage.py emit_signals_from_fixture --type zendesk --team-id 1 --offset 26 --limit 14142# Clean up all signal data before re-emitting (avoids stale matches)43DEBUG=1 python manage.py cleanup_signals --team-id 1 --yes4445# Check pipeline status46python manage.py signal_pipeline_status --team-id 1 --wait --expected-signals 1 --poll-interval 1047```4849Always clean up before re-emitting to avoid stale embeddings causing phantom report matches.5051## Monitoring Temporal workflows5253The Temporal UI runs at `http://localhost:8081`. The REST API is useful for scripted inspection.5455### List recent workflows5657```bash58curl -s 'http://localhost:8081/api/v1/namespaces/default/workflows?query=ORDER+BY+StartTime+DESC&maximumPageSize=15' \59 | python3 -c "60import sys, json61for wf in json.load(sys.stdin).get('executions', []):62 info = wf['execution']63 status = wf['status'].replace('WORKFLOW_EXECUTION_STATUS_', '')64 print(f'{wf[\"startTime\"][:19]} {status:20s} {wf[\"type\"][\"name\"]:35s} {info[\"workflowId\"][:90]}')65"66```6768### Inspect workflow history6970```bash71WF_ID="buffer-signals-1" # or team-signal-grouping-v2-1, signals-report:1:<uuid>72curl -s "http://localhost:8081/api/v1/namespaces/default/workflows/$WF_ID/history?maximumPageSize=200" \73 | python3 -c "74import sys, json75for event in json.load(sys.stdin).get('history', {}).get('events', []):76 etype = event['eventType'].replace('EVENT_TYPE_', '')77 etime = event['eventTime'][:19]78 details = ''79 for key, attrs in event.items():80 if key.endswith('Attributes') and isinstance(attrs, dict):81 if 'activityType' in attrs: details = attrs['activityType'].get('name', '')82 elif 'signalName' in attrs: details = f'signal: {attrs[\"signalName\"]}'83 elif 'startToFireTimeout' in attrs: details = f'timer: {attrs[\"startToFireTimeout\"]}'84 elif 'failure' in attrs: details = f'FAILED: {attrs[\"failure\"].get(\"message\", \"\")[:200]}'85 if details: print(f' {etime} {etype:50s} {details}')86"87```8889### Inspect a previous run (continued-as-new)9091When a workflow has continued-as-new, use the `execution.runId` query param:9293```bash94curl -s "http://localhost:8081/api/v1/namespaces/default/workflows/$WF_ID/history?execution.runId=<run-id>&maximumPageSize=200"95```9697## Reading sandbox agent logs9899Agent logs are stored in object storage (MinIO locally) as JSONL files.100The log URL is on the `TaskRun` model.101102```python103# In Django shell (python manage.py shell)104from products.tasks.backend.models import TaskRun105from posthog.storage import object_storage106107# Find the most recent task run108run = TaskRun.objects.order_by("-created_at").first()109print(f"status: {run.status}, error: {run.error_message}")110print(f"log_url: {run.log_url}")111112# Read the log113content = object_storage.read(run.log_url, missing_ok=True)114115# Print last 3000 chars (most useful — shows what happened before failure)116print(content[-3000:])117```118119The log is JSONL with entries like:120121```json122{123 "type": "notification",124 "timestamp": "...",125 "notification": { "jsonrpc": "2.0", "method": "_posthog/console", "params": { "level": "debug", "message": "..." } }126}127```128129Key things to look for in the log tail:130131- **agentsh network events** — `DENY` entries show blocked network calls132- **`_posthog/progress`** events — show which setup step the sandbox reached133- **`_posthog/console`** debug messages — show sandbox provisioning, cloning, agent startup134135## Inspecting Docker sandbox containers136137```bash138# List running sandbox containers139docker ps --filter "name=task-sandbox" --format "table {{.Names}}\t{{.Status}}\t{{.Image}}"140141# See processes inside a running sandbox142docker exec <container-name> ps aux143144# Read the agent-server log inside the container (while it's still running)145docker exec <container-name> cat /tmp/agent-server.log146```147148The container is named `task-sandbox-<task-id>-<random>` and uses the `posthog-sandbox-base` image.149Containers are ephemeral — they're removed after the task run completes, so inspect while running.150151## Common failures152153### `SignalReport matching query does not exist`154155The `assign_and_emit_signal_activity` tried to assign a signal to a report that doesn't exist.156Usually caused by stale embeddings in ClickHouse after a `cleanup_signals` that failed to delete them.157158**Root cause:** `CLICKHOUSE_DATABASE` not set in `.env`. The cleanup command uses `sync_execute`159which connects to the `CLICKHOUSE_DATABASE` (defaults to `default`), but the embedding tables160live in the `posthog` database.161162**Fix:** Add `CLICKHOUSE_DATABASE=posthog` to `.env` and restart workers.163164**Manual cleanup of stale embeddings:**165166```bash167curl -s 'http://localhost:8123/' --data-binary \168 "ALTER TABLE posthog.sharded_posthog_document_embeddings_text_embedding_3_small_1536 DELETE WHERE product = 'signals' AND team_id = 1 SETTINGS mutations_sync = 1"169```170171**Verify embeddings are clean:**172173```bash174curl -s 'http://localhost:8123/' --data-binary \175 "SELECT count() FROM posthog.sharded_posthog_document_embeddings_text_embedding_3_small_1536 WHERE team_id = 1 AND product = 'signals'"176```177178### `Run timed out due to inactivity` on `select_repository_activity`179180The sandbox Claude agent went idle for longer than `TASKS_INACTIVITY_TIMEOUT_SECONDS`. When unset181this falls back to a 2 hour timeout — set `TASKS_INACTIVITY_TIMEOUT_SECONDS=30` locally to force fast failures.182183**Diagnosing:** Read the agent log from object storage (see above). Check the tail for:1841851. **agentsh network denials** — `DENY host.docker.internal` means the MCP server URL is blocked186 by the sandbox network policy. The `SIGNALS_REPO_DISCOVERY` environment's domain allowlist187 doesn't include `host.docker.internal`.1882. **No log content at all** — sandbox failed to start, check Docker container logs.1893. **Claude API errors** — check if `ANTHROPIC_API_KEY` is valid.190191### `buffer-signals` sits idle, never receives signals192193The `signal-emitter` completed but `buffer-signals` never got the `submit_signal`.194This happens when the emitter sent the signal to a previous buffer run that then continued-as-new,195and the new run started fresh without the pending signal. Re-emit the signal.196197### ClickHouse embedding tables "not found" during cleanup198199The tables exist in the `posthog` database but `sync_execute` queries the `default` database.200201```bash202# Verify tables exist203curl -s 'http://localhost:8123/' --data-binary "SHOW TABLES FROM posthog LIKE '%embed%'"204205# Check current CLICKHOUSE_DATABASE setting206grep CLICKHOUSE_DATABASE .env207```208209## Useful management commands210211| Command | Purpose |212| -------------------------------------------------- | ---------------------------------------------- |213| `emit_signals_from_fixture` | Emit test signals from JSON fixtures |214| `DEBUG=1 cleanup_signals --team-id N --yes` | Delete all signal data and terminate workflows |215| `signal_pipeline_status --team-id N --wait` | Wait for pipeline to finish processing |216| `list_signal_reports --team-id N --signals --json` | Inspect grouping results |217| `ingest_signals_json <file> --team-id N` | Ingest pre-processed signals from JSON |218| `ingest_report_json <file> --team-id N` | Seed a pre-researched report (skip sandbox) |219220## Key file locations221222- Pipeline workflow definitions: `products/signals/backend/temporal/`223- Buffer workflow: `products/signals/backend/temporal/buffer.py`224- Grouping workflow: `products/signals/backend/temporal/grouping_v2.py`225- Report summary workflow: `products/signals/backend/temporal/summary.py`226- Docker sandbox implementation: `products/tasks/backend/services/docker_sandbox.py`227- Sandbox Dockerfiles: `products/tasks/backend/sandbox/images/`228- Agent log polling: `products/tasks/backend/services/custom_prompt_internals.py`229- Cleanup command: `products/signals/backend/management/commands/cleanup_signals.py`230- Management command docs: `products/signals/backend/management/CLAUDE.md`231
Full transparency — inspect the skill content before installing.