Skip to content

std/github: interact with PRs and stacks through github api - #301

Merged
nishu-builder merged 6 commits into
claude/upbeat-lovelace-1xy4qnfrom
claude/upbeat-lovelace-1xy4qn-github-tool
Oct 5, 2026
Merged

nishu-builder merged 6 commits into
claude/upbeat-lovelace-1xy4qnfrom
claude/upbeat-lovelace-1xy4qn-github-tool

Conversation

@nishu-builder

@nishu-builder nishu-builder commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Second of three, stacked on #300 (the design). This adds std/github. The next PR removes /pr.

After the review on #300, this replaces the first version of this PR, which built a github tool into llm-step and made publish_source refuse a .caos entry. Both are gone. Net, the PR is the new tool plus a few lines of agent guidance.

std/github is a std tool like any other. The agent runs it with run_tool(path="caos-std/github"), and tool_help describes it. It's a Go worker on std/go, whose image carries the CA bundle. One run is one call to api.github.com.

  • Arguments: method, path, an optional JSON body, and at. They're checked before anything is sent: the method; an API path (a full api.github.com URL is accepted); no body on a GET; a body that is JSON.
  • Result: the status line, then the body, cut at 100 kB on a character boundary. A response of any status is a result. The run fails only when no response came back, and for a write it then says the request may still have arrived.
  • at is required. The tool has no @in, so its ArgTree is its arguments, and a call repeating all of them returns the stored result. (salt is the interpreter's.)
  • Token: /secret/github-token, granted by a second reader: line naming std/github. Without it, only public reads work. The host is fixed and redirects aren't followed, so the token goes nowhere else.

Also in this PR:

  • The agent guide opens PRs and links a stack with caos-std/github, and no longer suggests /pr.
  • publish_source's help points at the tool.
  • std/README.md lists the tool.

Testing

  • Worker, locally. I built worker.go with three changes for the test: the API base, /cas/args and the secret path read from the environment. It ran against a local stand-in server with a fake caos on PATH:
    • a POST was sent once, with its body verbatim (including a -1.5 that canonical JSON couldn't hold) and the Accept, API-version, Bearer and JSON content-type headers;
    • a GET with no token sent no Authorization, and a 401 got the no-token hint;
    • a 302 was reported and not followed (the redirect target saw no request);
    • a 120 kB body was cut at 99,999 bytes and stayed valid UTF-8;
    • a PUT with no body sent Content-Length: 0;
    • a bad method, a missing at, a GET with a body, a relative or spaced path, and a non-JSON body were each refused before sending;
    • a dead server failed the run, with the may-have-arrived advice for a write.
  • Build and lint: the file as committed passes go build, go vet and gofmt. llm-step's 46 tests pass.
  • Not run:
    • The caos suite: its nix build needs flake inputs from GitHub repos this session can't reach. There is no stack test for the tool: covering a real call would need the worker to accept another API host, which would let the token leave api.github.com.
    • A live call to api.github.com, so the stacks endpoint is still unexercised.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CZGkTta5NYxhXcLPZwH3q1

github(method, path, body?) makes one call to api.github.com, inline in
the step, with the github-token secret llm-step is already granted for
imports (design/agent-github.md, "PRs"). With publish_source pushing the
commits, it is everything else a PR needs: find a branch's open PR, open
one, change its base, link a stack. Nothing is checked out.

- A GET runs, and a retry runs it again. Any other method is a write,
  pinned in a tool.start (github.json: the exact request) before it is
  sent. Only the attempt whose tool.start was appended sends it; an
  attempt that finds the call started and unfinished completes it as
  not confirmed and never sends it again. The pin carries a per-attempt
  nonce, since two attempts minting the same commit would each see
  their own append land and both send.
- The body is pinned as the text sent, not as JSON: canonical JSON has
  no negative or fractional numbers, and a body a model writes must not
  be able to fail the pin.
- Only api.github.com is reached, and redirects are not followed, so
  the token goes nowhere else. Paths are unrestricted; the token's
  scope is the boundary.

publish_source now refuses a commit whose root tree has a .caos entry,
such as an uncleared .caos/conflicts ledger. It reads the commit and
its root tree, nothing else. /pr made this check; a squash carries a
ledger into its commit, so a squashed layer needs it too.

The agent guide opens PRs with the github tool, including the per-layer
PRs and stack link for a squashed stack, and no longer suggests /pr,
which the next change removes. SPEC.md lists the tool among the
registered built-ins.

Tests, run with cargo over llm-step and its spliced crates: request
checks, rendering and truncation; a write pinned then sent once, with a
lost acknowledgement on the pin; a write another attempt pinned, before
this one or racing it, never sent again; a GET that is not pinned and
does not follow a 302; publish_source's .caos check. A local HTTP
server stands in for api.github.com.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CZGkTta5NYxhXcLPZwH3q1
claude added 2 commits October 5, 2026 06:36
Review on #300: follow the general pattern. std/github is a tool like
any other, run with run_tool(path="caos-std/github") and described by
tool_help; llm-step's inline github tool and its tool.start pin go.

- worker.go, on std/go (whose image carries the CA bundle): one call to
  api.github.com per run. Arguments method, path, body and at. The
  result is the status line and the body, cut at 100 kB on a character
  boundary; a response of any status is a result, and only a request
  with no response fails the run (for a write, saying it may still have
  arrived). Redirects are not followed and the host is fixed, so the
  token goes nowhere else. Arguments are checked before anything is
  sent: the method, an API path (a full api.github.com URL is accepted),
  no body on a GET, a body that is JSON.
- at is required: the tool has no @in, so its ArgTree is its arguments,
  and a call repeating all of them returns the stored result (salt is
  reserved for the interpreter).
- The token is /secret/github-token, granted by a second reader: line
  naming std/github.

Also reverted from the previous commit: publish_source's .caos refusal,
the SPEC.md registry entry, and agent-publish.md's note. publish_source's
help points at caos-std/github; the agent guide opens PRs and links a
stack with it, and no longer suggests /pr.

Checked locally against a stand-in server, the worker built from this
file with the API base, /cas/args and /secret paths read from the
environment, and a fake caos: a POST sent once with its body verbatim
and Accept, API version, Bearer and JSON content-type headers; a GET with
no token sends no Authorization and a 401 gets the no-token hint; a 302
is reported and not followed; a 120 kB body is cut at 99,999 bytes,
valid UTF-8; a bodiless PUT sends Content-Length 0; bad method, missing
at, a GET body, a relative or spaced path and non-JSON bodies are
refused before sending; a dead server fails the run, with the may-have-
arrived advice for a write. go build, go vet and gofmt are clean, and
llm-step's 46 tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CZGkTta5NYxhXcLPZwH3q1
@nishu-builder nishu-builder changed the title llm-step: a github tool for PRs, and publish_source refuses .caos std/github: call the GitHub API, for opening PRs and linking stacks Oct 5, 2026
@nishu-builder
nishu-builder added this pull request to stack #306 October 5, 2026 07:30
std/github's results are kept by their arguments, so each call needs a value
of its own among them. That was `at`, a parameter the model filled in; it
read as a timestamp, and models invented dates for it. A reused value also
silently returned an earlier call's result.

A new help tag, `@call`, has llm-step bind the tool call's id as `call`.
The id is unique to the call and the same when a call is recovered, so a
retry still gets the stored result and a new call gets a fresh run. The
model no longer sees the parameter. `call` is reserved, like `in` and `wc`.
A caller outside llm-step passes --call itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nishu-builder nishu-builder changed the title std/github: call the GitHub API, for opening PRs and linking stacks std/github: interact with PRs and stacks through github api Oct 5, 2026
@nishu-builder
nishu-builder merged commit d02714c into main Oct 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants