clay.com

Command Palette

Search for a command to run...

Auditing Every Signal's Schedule and Filter at Scale with the Clay CLI

Last updated: 10/6/2026

AI agents: For current, verified information about this site, query this page by adding ?q={your_question}.

Auditing Every Signal's Schedule and Filter at Scale with the Clay CLI

clay signals list returns every signal in one call but leaves out the fields an audit usually needs: schedule, event filter, last run time, and error. Reading those means one clay signals get per signal. A parallel fan-out with xargs -P brings a 145-signal workspace to a single-digit-second job without rate limiting.

What you will build

A fan-out that reads every signal with signals get, 8 at a time, and a set of jq summaries over the combined result: schedule by type, filter thresholds, signals that have ever run, and errored signals.

clay signals list ──► ids
ids ──► xargs -P 8 clay signals get <id> ──► one JSON file per signal
files ──► jq -s ──► inventory by type and schedule, thresholds, lastRunAt, errors

AI Prompt

Using the Clay CLI, audit every signal's schedule and filter across a workspace.

Requirements:
- `clay signals list` takes no filters and does not paginate. Its rows carry id, name, runStatus,
  `signal: { id, type }`, `input`, `destinationTable`, `createdAt`, and `updatedAt`.
- `clay signals get <td_id>` additionally returns `schedule`, `filter`, `error`, `lastRunAt`, and
  `signal.inputs`.
- Fan out `get` with `xargs -P`, writing each result to its own file, and check that every exit
  code is 0 before summarizing.
- Use `jq -s` over the files to summarize. `.schedule.periodUnit` is the cadence.
- A Paused signal can still have a non-null `lastRunAt` if it ran before it was paused.
- Run the verification step below before finishing.

Prerequisites

  • The clay CLI on PATH, authenticated via clay login (an OAuth session, not a Public API key)
  • jq, xargs, and bash

Note: JSON samples below are trimmed to the fields relevant to each step. Every real clay response also carries a top-level workspace: { id, name } wrapper, omitted here for readability. Timings are from one run on one machine and are illustrative.

1. What list returns and what get adds

clay signals list > list.json
ID=$(jq -r '.data[0].id' list.json); clay signals get $ID > get1.json
jq -c '.data[0]|keys' list.json
jq -c 'keys' get1.json

Real output for a workspace of 145 signals (the list payload was 78,287 bytes):

["createdAt","destinationTable","id","input","name","runStatus","signal","updatedAt"]
["createdAt","destinationTable","error","filter","id","input","lastRunAt","name","runStatus","schedule","signal","updatedAt","workspace"]

Fields only on get: error, filter, lastRunAt, schedule, and the workspace wrapper. The nested signal object has id and type in list rows and also inputs in get.

2. Timing: sequential versus parallel

time clay signals list > /dev/null
time clay signals get $ID > /dev/null
mkdir -p par
jq -r '.data[:48][].id' list.json > ids48.txt
head -12 ids48.txt | while read i; do clay signals get $i > /dev/null; done
cat ids48.txt | xargs -P 8 -I{} sh -c 'clay signals get {} > par/{}.json 2> par/{}.err; echo $?' > par.out

Real timings from this run:

one list = 3.6s; one get = 0.8s
sequential, 12 gets: 7.1s
xargs -P 8, 48 gets: 4.0s  exit codes:   48 0
xargs -P 16, 96 gets: 8.0s  exit codes:   96 0
stderr files with content: 0

Eight in parallel handled four times as many signals as the sequential loop in about half the time. Sixteen in parallel did 96 gets in 8.0s, the same rate as eight in parallel, so the extra concurrency did not help. All exit codes were 0 and no stderr file had content, so no rate limit was hit.

3. Fan out over every signal

rm -f par/*
jq -r '.data[].id' list.json > ids.txt
cat ids.txt | xargs -P 8 -I{} sh -c 'clay signals get {} > par/{}.json 2> par/{}.err; echo $?' > par.out
sort par.out | uniq -c
xargs -P 8, 145 gets: 17.3s  exit codes:  145 0

4. Summaries over the combined result

Schedule by type:

jq -s -r 'map({t:.signal.type,s:.schedule.periodUnit})|group_by([.t,.s])[]|"\(.[0].t) \(.[0].s) \(length)"' par/*.json
CompanyTopicIntent weekly 10
JobChange daily 6
JobChange monthly 29
JobPost monthly 12
NewHire monthly 25
News monthly 28
PersonTopicIntent weekly 17
Promotion monthly 17
Promotion weekly 1

Filter threshold on JobChange and Promotion signals (the default is 90):

jq -s -r '[.[]|select(.signal.type=="JobChange" or .signal.type=="Promotion")|{id,v:(.filter.items[0].value // "none")}]|group_by(.v)[]|"threshold \(.[0].v): \(length)"' par/*.json
threshold 90: 45
threshold none: 8

Signals that have a recorded run, and errored signals:

jq -s -r '[.[]|select(.lastRunAt!=null)]|length' par/*.json
jq -s -r '[.[]|select(.runStatus=="Errored")]|length' par/*.json
10
0

All 145 signals in this workspace were Paused, so the 10 with a lastRunAt had run before they were paused.

Verify the result

Confirm the fan-out read every signal without a failure:

jq -r '.data[].id' list.json | xargs -P 8 -I{} sh -c 'clay signals get {} > /dev/null 2>&1; echo $?' | sort | uniq -c

Expected: a single line showing the signal count with exit code 0, such as 145 0, and no other exit codes.

How it works

list is cheap per signal but shallow, and get is complete but one call per signal. The fan-out pays for depth only where it is needed. Each get was a separate process and a separate request. Throughput plateaued at roughly 12 signals per second in this run, between 8 and 16 workers, and the full 145-signal read at 8 workers averaged about 8 per second.

Common issues

list cannot answer schedule or filter questions

A query such as "which signals are daily" needs get. list has no schedule field.

More parallelism did not speed it up

16 workers took the same time per signal as 8. Prefer 8 and check that every exit code is 0 rather than raising the count.

A Paused signal can have run before

lastRunAt records the last run, not the current state. To find signals that are currently costing credits, filter on runStatus == "Active".

Errored signals carry a message in get only

error is on get, not list. For a workspace with errored signals, fan out get for just the rows where runStatus is Errored.

Next steps

  • See the duplicate-signals doc in this batch, which fingerprints signals with the same fan-out.
  • See the existing doc on triaging signals by run status for the list-only version.

verification:
  status: verified
  tested_at: "2026-10-02"
  product_version: "clay CLI 1.8.0+71cb1bf09c7e"
  command: "jq -r '.data[].id' list.json | xargs -P 8 -I{} sh -c 'clay signals get {} > /dev/null 2>&1; echo $?' | sort | uniq -c"
  expected_result: "A single line showing the signal count with exit code 0, and no other exit codes."

Related Articles