Skip to content

docs: shell-scripting gotchas for bulk ops against the agent API #41

@rossgroomio

Description

@rossgroomio

Posting here as the public-facing SDK repo — happy to re-route if the canonical SKILL.md source lives elsewhere.

While scripting a bulk comment/reply loop (14 comment.reply + comment.resolve ops) against a hosted Proof doc via the agent API (/api/agent/<slug>/ops), I hit four shell-level gotchas that aren't covered by the SKILL.md served at https://www.proofeditor.ai/proof.SKILL.md. Each one fails silently or with an obscure error rather than a clean API error.

Worth a short "Shell Patterns" section in the skill doc, or a linked appendix — authors of agent loops keep rediscovering these.

1. .marks is an object, not an array

The /state and /snapshot response's marks field is keyed by markId, not indexed. jq '.marks[0]' fails with Cannot index object with number. MarkIds look like m1776098130482_1 (comments) or authored:human:<name>:<range> (text anchors).

# Wrong
jq '.marks[0]' state.json

# Right
jq '.marks | to_entries[]' state.json

2. for row in $(jq -c '.[]' file) word-splits JSON

Bash splits $(...) on whitespace, including spaces inside JSON string values. Any op payload with a space in text or content fragments across loop iterations. Use a while-read pipe:

jq -c '.[]' ops.json | while IFS= read -r row; do
  curl ... -d "$row"
done

3. baseToken rotates per mutation — refetch inside loops

The current SKILL.md documents baseToken and the 409 BASE_TOKEN_REQUIRED retry path well. The missing piece: for a loop of N ops, a single upfront fetch of mutationBase.token from /snapshot goes stale after op 1 and ops 2..N all fail with Document changed since baseToken until you refetch. Worth calling out explicitly as "refetch per iteration for bulk loops" alongside the existing retry-on-409 guidance.

4. jq parse failure on the response ≠ API failure

Op responses occasionally contain control characters (U+0000–U+001F) that break jq parsing with Invalid string: control characters ... must be escaped. The server-side op may have landed fine. Recommended pattern: fall back on parse failure and verify against /state rather than trusting the parse exit code.

RESULT=$(curl -sS -X POST ".../ops" --data @op.json 2>/dev/null)
STATUS=$(echo "$RESULT" | jq -r 'if .ok == true then "ok" elif .error then .error else "unknown" end' 2>/dev/null || echo "parse-fail-check-state")
# Then re-read /state to confirm the op landed

Happy to open a PR

Can you point me at the canonical source for the SKILL.md served at proofeditor.ai/proof.SKILL.md so I can put up a PR with these as a new section? I couldn't find an obvious match in this repo's docs/proof.SKILL.md — that file describes a /documents/<slug>/edit/v2 + baseRevision API surface rather than the agent API my scripting session used, which suggests the hosted SKILL.md comes from somewhere else.

Reproduced on hosted Proof, 2026-04-13.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions