The agent
thespider-agent is a small local binary. It logs you in, claims work, runs your model CLI in the checkout, and posts the output. Prompts, parsing, and findings live on the server.
Install the skill (optional)
npx skills add https://thespider.xyz -g
Then ask a coding agent "Get me started with TheSpider." or "What's the next TheSpider step in this repo?" On-prem, pass your instance origin.
Commands
thespider-agent setup [--model <lab/model>] [--agent grok|claude|codex|opencode|<any>] [--command TPL]
[--profile security|bugs] [--project org/slug]
thespider-agent check-models
thespider-agent login | init | logout | status | models | run | gate
thespider-agent daemon [--interval SECS] [--workdir DIR] [--models a,b] [--profile P] [--redact]
thespider-agent findings list|show|triage|link|unlink|packet|copy
thespider-agent log list|show|path|clean
thespider-agent wait --campaign <id>
thespider-agent refine [--max-iterations N] [--min-severity SEV] [--list]
thespider-agent update
--model names the model (models.dev code). --agent names the local CLI. --code / --harness are deprecated aliases. --model claude does not select the Claude CLI; use --agent claude.
Setup and a first run
cd /path/to/repository
thespider-agent setup --model xai/grok-4.6 --agent grok --profile bugs
# Omit --model on a terminal to search models.dev.
thespider-agent run --once --max-slices 1
| Flag | Default | Meaning |
|---|---|---|
--model | interactive search | models.dev code, e.g. xai/grok-4.6 |
--agent | auto-detect (Grok first) | grok, claude, codex, opencode, or any other name with --command |
--command | none | Required for an agent TheSpider does not know. {prompt_path} is the slice prompt. |
--model-id | derived from --model | Override only if the CLI's alias differs |
--variant | none | OpenCode --variant only |
--refresh | off | Re-download the models.dev catalog |
--agent and --model are independent: --agent codex --model anthropic/claude-opus-5 is fine.
An agent TheSpider does not know:
thespider-agent setup \
--model alibaba/qwen3.8-max \
--agent pi \
--command 'pi run --model qwen3.8-max {prompt_path}'
If setup enables a model other than the starter model (xai/grok-4.6) that new projects come with, it also disables that starter — but only while nobody has chosen it or run it in the project, and it says so. Setup will not overwrite an existing freeform command on that code. With several org memberships it asks which org. It only auto-picks a project on an exact remote match or an explicit --project.
Check local CLIs
thespider-agent check-models
Offline and free: no server call, no model call. It probes the four built-in CLIs (version and login status, 10-second timeout each) and lists whether each project binding's command is on PATH. Exit 1 only when a built-in agent a binding uses is missing or logged out. Grok cannot answer an auth probe, so that line is "unsupported", not a blocker.
thespider-agent models is different: it compares local bindings with the models the server has enabled.
Turn a model off
setup --model enables a model for the project. models disable is the way back, so a terminal-only, scripted or agent-driven setup never needs the web UI:
thespider-agent models disable xai/grok-4.6 --dry-run # what would change; writes nothing
thespider-agent models disable xai/grok-4.6 --yes # do it (off a terminal, --yes is required)
thespider-agent models enable xai/grok-4.6 # the reverse
Disabling does three things in one step: the server stops offering the model, its unfinished slices are skipped (pending ones, failed ones with retries left, and ones whose lease already expired), and the matching [[model]] block is removed from .thespider/config.toml. Slices running on a live lease are left to finish; the command reports how many. Findings the model already reported stay, with their attribution.
Skipping is what unblocks a campaign planned with a model nobody binds: it can finish, and wait and the PR gate reach a verdict. A campaign that finishes with skipped slices reads degraded (wait and gate exit 4), never a clean pass, because that coverage was never audited. A new finding still outranks it.
| Flag | Effect |
|---|---|
--dry-run | Print the profiles, skipped slices and config edit; change nothing on the server or in the file. |
--yes, -y | Consent without prompting. Off a terminal it is required (exit 5 without it). |
--profile security|bugs | Disable for one profile only (default: all). The local [[model]] block is kept, because the model may still be enabled for the other profile. |
--project org/slug | Act on another project. This checkout's config is not edited. |
--keep-local | Leave the [[model]] block in the file. |
--json | Emit the versioned envelope. |
Disabling an already-disabled model exits 0, and still skips anything an earlier disable (for example one made in the web UI before slices were skipped) left behind. It needs an organization owner or admin; anyone else gets an error that names the role. Exit codes: 0 ok, 1 the server does not know that model code, 5 usage (bad argument, or confirmation needed or declined), 6 operational.
enable needs no confirmation: naming the code is the consent, as with setup --model. It does not add a local binding (setup --model <code> --agent <agent> does). Slices skipped by an earlier disable return to pending the next time run creates or reuses that campaign.
thespider-agent status covers both plus the work that is actually waiting. Its readiness: line
states what the next campaign on this profile would plan (buckets × passes × enabled models) —
that is a different denominator from a running campaign's "13 of 24", which is fixed at the config
version that campaign was created with. Its remaining: block finds the newest campaign with
claimable slices and splits it by model, marking each code bound or unbound in this checkout,
and names any invalid coverage waiting for repair.
Run
thespider-agent run [--campaign <id>] [--profile security|bugs] [--once] [--max-slices N] [--models a,b] [--redact] [--target-path DIR] [--isolated]
Without --campaign, this creates a full campaign from local git metadata, planned only for the bound models it will claim (another agent binding a different model on the same commit adds its slices to that campaign), claims slices for those models, runs each with sh -c, and posts stdout. --once --max-slices 1 is the bounded first-run.
| Flag | Semantics |
|---|---|
| (none) | Drains the campaign: claim a batch, run it, repeat until nothing claimable is left. |
--campaign ID | Works that campaign instead of creating one. Its profile and target commit win; a contradicting --profile is a usage error, never a silent override. |
--once | Stops after one claim batch (concurrency × 4, capped at 25). |
--max-slices N | Stops after N slices. Combines with --once; the first bound reached wins. |
--models a,b | Narrows the claim to a subset of your [[model]] bindings. It can never add a model — a code with no local binding is unclaimable with or without it. |
--target-path DIR | The runner working directory and the project-config discovery root. |
Each bounded run prints a continue: line reproducing this invocation with the remaining count, so resuming never silently drops --isolated or --target-path.
Because a slice belongs to exactly one model, a run can stop with no claimable slices while the campaign still reports work. It then names the models the remainder does belong to, and whether they are bound here — see troubleshooting. A campaign holding invalid coverage also prints the repair --dry-run command on the way out.
Before the first claim, the agent prints the resolved project file, target path, project selector, and profile — so which .thespider/config.toml won is never a guess.
If the target has gitignored .env*, key, or database files, the agent warns. Those files are not uploaded to TheSpider, but a local model agent can still read them and send tool output to its provider. --isolated runs the model in a detached worktree of the audited commit so those ignored files are not in the tree.
Daemon
For team / PR CI: one long-lived process on a box that has model credentials and git access. CI never runs a model. See PR gate.
thespider-agent daemon [--interval 30] [--workdir DIR] [--models a,b] [--profile P] [--redact]
It polls claimable campaigns, clones the exact commit into a managed checkout, then claims and runs like run. SIGINT / SIGTERM finish the in-flight campaign and stop. Git auth is whatever that OS user already uses (ssh-agent or an HTTPS helper).
Logs
thespider-agent log list [--slice N] [--limit N] [--all-servers] [--project org/slug] [--all-projects]
thespider-agent repair --campaign ID [--dry-run]
thespider-agent log show <slice-id> [--lease N] [--stream stdout|stderr|both] [--follow] [--timeout SECS]
thespider-agent log path <slice-id> [--lease N]
thespider-agent log clean (--older-than-days N | --all) [--all-servers] [--include-running] [--dry-run]
Every lease attempt writes its own directory on the machine that ran the model: <data dir>/thespider/logs/<server id>/<slice id>-<lease id>/, holding meta.json, stdout.log, stderr.log, and a terminal result.json. Payload files append the model's bytes without rewriting them; missing bytes are described below. Nothing here is uploaded, needs auth, or talks to the server, and a read never creates a directory.
The positional argument is a slice id, as printed by run and daemon. A leading # is accepted, because that is how log list prints it (slice #88 lease #12) — log show 88 and log show '#88' are the same command, and both take the newest attempt; --lease N pins one. Show and path read only the current project config's server and name that namespace on stderr. To read another server's row from an all-server listing, run in that server's configured project. log path 88 prints just the directory to stdout for your own tail -f. Show sanitises model output, stripping C0 controls except \n, \t, and \r, C1 bytes, and ESC-introduced escape sequences. The files under log path are the byte-faithful route.
--follow exits 0 after a parsed terminal record and the final payload drain. Its 1800-second default timeout covers both waiting for creation and following, as one budget. If a result-less writer PID is confirmed gone, list shows interrupted; follow drains buffered output, prints an interruption note, and exits 7 without creating result.json. Unverified probes remain conservatively running and are labelled liveness unverified. Plain show only dumps bytes: exit 0 does not certify completion. A downstream closed pipe, such as | head or quitting less, ends show/follow with 0; consume the full output when relying on exit 7.
Exit codes: 0 ok, 1 no log for that selector, 3 follow timed out, 5 usage, 6 operational, 7 followed writer interrupted. Operational errors include an unreadable root or namespace, an entry-inspection or walk I/O failure, a selected lease that cannot be removed, an unreadable terminal during follow, or a log directory removed during follow. An incomplete list prints available rows but exits 6. A missing store is 1, not 6. A corrupt meta.json is skipped with a warning; after inspection, log clean --all --include-running can reclaim it. Add --all-servers to recover another namespace.
Retention is manual and local: nothing sweeps these directories, each stream is capped at 32 MiB, and log clean --older-than-days 14 (or --all) is the only collector. log list defaults to the project in the active .thespider/config.toml. --all-projects widens that; --project org/slug pins one. Rows that span projects include the project selector. A row showing (project not recorded) was written by an agent older than 0.5.0, before the selector was stored; such rows are shown under any project filter rather than hidden, and log list says so once on stderr. Clean defaults to the current repo's server namespace. Add --all-servers to sweep every local namespace without resolving the current server first. This flag only widens scope, so it still needs --older-than-days N or --all. The sweep only enters 16-character lowercase hexadecimal server namespaces, continues after a namespace failure, reports an aggregate, and exits 6 if incomplete. Unrelated directories under an operator-supplied log root are untouched. Without --include-running, all result-less leases are kept, including writers shown as interrupted. This applies to both selectors and every server scope.
A present but unreadable or malformed result.json is reported as terminal record unreadable; it is not classified from PID liveness and default cleanup keeps it. Missing or unreadable metadata is tallied separately and requires --include-running. Control files must be regular, non-symlink files of at most 1 MiB.
All listing scopes only read real canonical <slice>-<lease> directories, not lease symlinks. Filename IDs and both metadata server identities must match for listing and ordinary deletion. All-server list and clean also require real 16-character lowercase hexadecimal namespace directories, never namespace symlinks. Explicit --all --include-running recovery can delete canonical leaves with missing or unreadable metadata, but never overrides readable mismatched identities. Clean covers all projects with no row limit in the selected server scope, unlike list's project filter and default 50-row limit. Use clean's --dry-run as the deletion preview.
The default app-created log root is private. A custom root must not be writable by untrusted users: cleanup revalidates namespaces and leaves before traversal and removal, but these checks are not atomic against hostile mutation. PID probing is local to the writer's machine and PID namespace. Shared roots across hosts or containers can make a live writer look gone in list/follow; keep logs machine-local for meaningful classification. Default cleanup never uses PID liveness as permission to delete a result-less lease. PID reuse can make interrupted leases appear running and delay follow until timeout. After verifying the writer has stopped, --include-running permits reclamation. Logging is best-effort: if terminal persistence and its Drop retry both fail, the result-less log is indistinguishable from an interrupted run. Default cleanup retains both.
thespider-agent repair --campaign ID requeues completed slices whose winning result was not valid coverage (nonzero exit, missing marker, bad JSON). Valid empty arrays are left alone. --dry-run only prints the valid / invalid / unverified counts. THESPIDER_LOG_DIR moves the root; THESPIDER_NO_LOGS=1 turns lease logging off. On Windows, %APPDATA% roams — point THESPIDER_LOG_DIR somewhere local.
Lease logs and --redact
The agent keeps a per-lease run log on your machine under its data directory: the model's stdout and stderr, plus a small metadata and result record. Nothing is uploaded; this is separate from the artifact the server already stores.
--redact masks secret-shaped strings in stdout (and caps stdout at 1 MiB) and in stderr before upload, and it governs the local copy by exactly the same rule, so the flag means one thing everywhere. Both streams follow the flag:
- stdout follows
--redacton both sides. Without the flag, the log holds the model's raw stdout, streamed as it arrives — that is what makeslog show --followlive. With the flag, the log holds the same masked, 1 MiB-capped buffer that is uploaded. - stderr follows
--redacton both sides. Without the flag, the log holds the raw stderr stream and the upload carries its raw last 4000 bytes. With the flag, both hold the masked stream (the upload carries the last 4000 bytes of the masked stream).
Under --redact the payload is not live: nothing reaches the disk while the model runs, and both files are written once, when the model exits, so log show --follow reports the lease's state and liveness during the run and delivers the output at the end. That is a deliberate trade, and --redact is off by default.
The local stderr.log holds the model's complete stderr, while the upload carries only its last 4000 bytes — the local copy is the bigger exposure of the two, not the smaller. Both local streams are capped at 32 MiB each; past that the agent stops appending and records the truncation in the result record.
result.json tells apart the two ways a payload file can fall short. stdout_truncated with stdout_dropped_bytes: 0 is the cap — the file is a prefix of the stream. A non-zero stdout_dropped_bytes means the log filesystem could not keep up — it stalled for two full seconds, or cost the model's pipe thirty seconds of backpressure over the run — so the agent stopped teeing that stream rather than hold up the model, and bytes are missing. log_errors names the stream and a warning goes to stderr during the run. Only the local copy is ever affected — the artifact uploaded to the server is complete either way.
Read stdout_dropped_bytes and stderr_dropped_bytes to find out: they are authoritative and always correct. The file may also carry an in-band [lease log: N bytes dropped here] line where the bytes stop, but the thread that writes it is the one that was falling behind, so it lands only if the filesystem recovered before the lease ended. The absence of a marker is not evidence that the file is whole.
On Unix the log directory is 0700 and its files are 0600; on Windows they inherit the parent ACL, and note that %APPDATA% roams — set THESPIDER_LOG_DIR to keep multi-megabyte transcripts out of a roaming profile. Treat these files exactly as you would treat the raw artifact on the server. Nothing sweeps them: thespider-agent log clean is the collector, and THESPIDER_NO_LOGS=1 turns lease logging off entirely.
The server-side scrubber is a separate layer: it always runs on parsed findings and the stderr artifact, never on raw stdout artifacts, and never on anything the agent keeps locally. More: Data handling.
Findings
thespider-agent findings list --open --severity critical,high --json
thespider-agent findings list --since 25 # discovered AFTER campaign 25
thespider-agent findings list --since 2026-09-01 # discovered on or after that date (UTC)
thespider-agent findings show 501 --json
thespider-agent findings packet 501
thespider-agent findings triage 501 --status accepted --reason "risk accepted"
thespider-agent findings link 501 502 --mr 128 # after opening merge request !128 for them
thespider-agent findings list --actionable --unlinked # what still has no merge request
--since is incremental triage: "what did that run add". A bare integer is a campaign id and
selects findings discovered after it, so the campaign you name is the one you already reviewed;
anything else is an ISO date or RFC 3339 timestamp, compared against when the finding was first
discovered (never against a later triage edit). --campaign N is the different question — findings
sighted in that campaign, including ones it re-confirmed.
Filters run on the server before pagination. --json prints one JSON document on stdout, including on failure. Envelope and exit codes: Agent CLI contracts.
Wait vs gate
thespider-agent wait --campaign 81 --timeout 1800 --fail-on critical,high --json
thespider-agent gate --campaign 81
They are different tools. wait blocks until the campaign is idle, then fails on actionable findings. gate is a single poll for CI (0 pass / 1 fail / 2 pending / 4 degraded). Do not mix their exit codes. Details: PR gate.
Refine
refine is the fix-and-verify half of the loop. It picks actionable findings, has a local model write a fix, records the commits as a proposed fix (needs_retest), then runs a retest. Only a finished retest concludes fixed or still_vulnerable.
thespider-agent refine --min-severity high --max-iterations 2
Full flags, worktree, and threat model: Refine.
--redact masks secret-shaped stdout and stderr and caps stdout before upload. The server also scrubs parsed findings and the stderr artifact. Raw stdout artifacts store whatever was uploaded unless you pass --redact. It governs the agent's own lease log the same way — masked stdout, masked stderr. See Logs.