run-cc-backend
Build, start, run and drive cc-backend end to end — an isolated server with the internal metric store, ingest line protocol, start jobs, query GraphQL/REST, smoke-test metric queries and scopes. Use w
Files3
View on GitHubSKILL.md.claude/skills/run-cc-backend/SKILL.md
GitHubcc-backend is a Go HTTP server (REST + GraphQL + embedded Svelte UI). Agents drive it
through .claude/skills/run-cc-backend/driver.sh, which builds the binary, runs the
server in the background in an isolated run directory, and wraps curl for line-protocol
ingestion, job start, GraphQL and metric-store debugging.
All paths are relative to the repo root. The driver never touches the repo's ./var
(it may hold the user's real data): binary, config, DB, job archive and checkpoints all
live in $CC_RUN_DIR (default $TMPDIR/cc-backend-run).
Prerequisites
Verified on macOS (darwin/arm64) with Go 1.27.1; go.mod needs Go ≥ 1.26.3. Also needs
python3, curl and bash on PATH. The frontend does not need to be built: the
server, REST and GraphQL run fine without web/frontend/public/build (only the web UI
pages lack their bundles).
Run (agent path)
One command, end to end (~13 s; exit code 0 means healthy):
.claude/skills/run-cc-backend/driver.sh smoke
It runs setup, start, ingests 11 one-minute samples of fs_read_bw for /home (1) and
/scratch (2) on host h1, starts running job 4242, waits for it to be committed,
queries jobMetrics(scopes: [node, filesystem]), asserts per-mount series 1 and 2 and a
node series 3 without an id, all with no gaps, prints SMOKE OK, and stops the server. On a failed
assertion it prints FAIL: ..., exits 1, and still stops the server.
Step by step (a running job on h1 with ten minutes of per-mount data):
D=.claude/skills/run-cc-backend/driver.sh
$D setup # build, config, archive, DB, user tester/testpw, JWT
$D start # background server on 127.0.0.1:18080, waits for ready
NOW=$(python3 -c 'import time; print(int(time.time()) // 60 * 60)'); START=$((NOW - 600))
python3 -c "
for ts in range($START, $NOW + 1, 60):
for mnt, v in (('/home', 1), ('/scratch', 2)):
print(f'fs_read_bw,cluster=devcluster,hostname=h1,type=filesystem,type-id={mnt} value={v} {ts}')
" | $D write - # line protocol from stdin (or: $D write FILE)
cat > "${TMPDIR:-/tmp}/job.json" <<JSON
{"cluster":"devcluster","subCluster":"a","jobId":4243,"jobState":"running","numNodes":1,
"numHwthreads":4,"resources":[{"hostname":"h1"}],"startTime":$START,"submitTime":$START,
"user":"tester","project":"p","walltime":86400}
JSON
$D start-job "${TMPDIR:-/tmp}/job.json" # POST /api/jobs/start_job/
ID=$($D wait-job 4243) # db id once the job is committed
$D gql "{ jobMetrics(id: \"$ID\", metrics: [\"fs_read_bw\"], scopes: [node, filesystem]) { scope metric { series { id data } } } }"
$D debug h1 # metric store levels and buffers of host h1
$D reset-store # stop, wipe checkpoints, start with an empty store
$D stop # waits for the process to exit
| command | what it does |
|---|---|
setup |
rebuilds the run dir from scratch (refuses to delete a dir it did not create) |
start / stop |
background launch with readiness poll on /login; stop waits for the PID to exit |
write FILE|- |
POST /api/write?cluster=devcluster, Bearer JWT |
start-job FILE |
POST /api/jobs/start_job/ with a JSON payload |
wait-job JOBID |
polls GraphQL jobs until the job appears, prints its id |
gql 'QUERY' |
raw GraphQL query text, JSON-wrapped and posted to /query |
debug HOST |
GET /api/debug?selector=devcluster:HOST |
reset-store |
empty metric store, keeps DB and jobs |
smoke |
the full flow above, with assertions |
Overrides: CC_RUN_DIR=/some/dir and CC_PORT=18081 (both verified). Log:
$CC_RUN_DIR/server.log; token: $CC_RUN_DIR/token; last smoke result:
$CC_RUN_DIR/result.json.
The fixture cluster devcluster is .claude/skills/run-cc-backend/cluster.json:
subcluster a = host h1 (filesystems /home, /scratch, accelerators 0,1,
network ib0), b = h2 (/work), c = h3 (no devices). Metrics: flops_any
(hwthread), fs_read_bw (filesystem, sum), acc_util (accelerator, avg). Edit it to
test other topologies; setup copies it into the archive.
Run (human path)
./startDemo.sh and ./cc-backend -server -dev run against the repo's own
./config.json and ./var, which may hold the user's real data. This skill does not
exercise them; agents use the driver.
Test
go test ./...
All packages pass. The run rewrites internal/repository/testdata/job.db; restore it
with git checkout -- internal/repository/testdata/job.db before committing.
Gotchas
- Device data must be tagged
type=<device>,type-id=<id>(as for accelerators), e.g.fs_read_bw,cluster=devcluster,hostname=h1,type=filesystem,type-id=/home value=1 <ts>.type=node,stype=filesystem,stype-id=…lands in the host buffer itself, not on a device level, so filesystem-scope queries return nothing. - Running jobs are invisible to GraphQL until committed.
start_jobwrites to the job cache; the commit service moves it everycron.commit-job-worker(default 2 min). The driver config sets5s;wait-jobpolls for it. - Secrets in the caller's env override
config.json. An exportedJWT_PUBLIC_KEY/JWT_PRIVATE_KEY(or*_FILE) would make the minted token invalid; the driver unsets them for every invocation. - The final checkpoint is written after the port closes. Deleting
var/checkpointsas soon as the port is free resurrects old samples on the next start, and earlier timestamps are then rejected withcannot write value to buffer from past.stopandreset-storewait for the process itself. - Samples older than a buffer's start are dropped. Ingest each series in time order,
and use
reset-storebefore backfilling a window that starts earlier. - A running job's query window ends at "now". If a minute boundary passes after the
last ingested sample, the series gains a trailing
nullfor the open minute. That is correct, not data loss;smoketolerates trailing nulls but not gaps. - Fresh DB needs
-migrate-dbbefore-init-db;setupdoes both.
Troubleshooting
unsupported database version 0, need 13: the DB was never migrated. Runsetupagain (it migrates first).- Pipeline hangs after
start(e.g.driver.sh start | tail): a background process is holding the pipe. The driver redirects the server's stdio; if you launch the binary yourself, use( exec ./cc-backend ... ) > log 2>&1 < /dev/null &. - macOS
seqprints epoch seconds as1.79084e+09: every line gets the same broken timestamp and only one sample lands. Generate timestamps withpython3(assmokedoes). refusing to delete itfromsetup:CC_RUN_DIRpoints at a directory the driver did not create. Pick another path.