Skip to content

tools: add github_pr_create, lex-code's first approval-gated tool - #152

Merged
alpibrupa merged 2 commits into
mainfrom
feat/github-pr-tool
Sep 8, 2026
Merged

tools: add github_pr_create, lex-code's first approval-gated tool#152
alpibrupa merged 2 commits into
mainfrom
feat/github-pr-tool

Conversation

@alpibrupa

Copy link
Copy Markdown
Contributor

Summary

  • Every VCS tool in src/tools/vcs/ wraps lex-vcs (lex branch/lex merge), the structural, content-addressed system built into the lex CLI — none of them touch git or GitHub. Shipping a change from a lex-code run has always meant a human (or Claude, driving gh by hand outside this codebase) taking the current branch the rest of the way.
  • Adds github_pr_create: shells to gh pr create --title --body [--base] on the current (already-pushed) branch. Deliberately does NOT push the branch itself — that stays an ordinary bash/git action any mode with those tools already has.
  • This is also the first tool in the codebase to carry an approval_scope (t.with_approval, lex-llm#41 / lex-lang#737's std.approval effect) — grep -rln "approval_scope" src/tools/ returned nothing before this. Opening a PR is visible to everyone with repo access and cannot be un-opened the way a local edit can be un-written, so it's exactly the class of action every other agent gates behind a human; lex-code had the plumbing since Expose modes/tools as MCP server (lex-mcp run_http) #41 and nothing ever used it.
  • Wired into build (all_tools_for_mode, unrestricted) and refactor (refactor_permission's allowlist) — not into the local-model minimal/dynamic toolsets, matching the existing convention of keeping local models' visible surface small.
  • Found and fixed a real gap in lex-lang while validating this: lex run's CLI never wired StdinApprovalSink, so every approval request was unconditionally refused regardless of what stdin contained — fixed upstream in lex-lang#807.

Test plan

  • lex check / lex fmt --check / lex doc-sync --check — clean
  • lex test tests — 4/4 existing tests still pass
  • Verified end to end against the real dispatch path (after lex-lang#807): denying returns "denied by operator"; approving genuinely proceeds to gh pr create, which then fails for the expected unrelated reason (head branch same as base) — confirming the tool only ever runs after a real approval, not a stubbed one

🤖 Generated with Claude Code

alpibrupa and others added 2 commits September 8, 2026 17:26
Every VCS tool in src/tools/vcs/ wraps lex-vcs (lex branch/lex merge),
the structural, content-addressed system built into the lex CLI --
none of them touch git or GitHub. Shipping a change from a lex-code
run has always meant a human (or Claude, driving `gh` by hand outside
this codebase) taking the current branch the rest of the way.

Adds github_pr_create: shells to `gh pr create --title --body [--base]`
on the current (already-pushed) branch. Deliberately does not push the
branch itself -- that stays an ordinary bash/git action any mode with
those tools already has.

This is also the first tool in the codebase to carry an approval_scope
(t.with_approval, lex-llm#41 / lex-lang#737's std.approval effect) --
`grep -rln "approval_scope" src/tools/` returned nothing before this.
Opening a PR is visible to everyone with repo access and cannot be
un-opened the way a local edit can be un-written, so it is exactly the
class of action every other agent gates behind a human; lex-code had
the plumbing since #41 and nothing ever used it.

Wired into build (all_tools_for_mode, unrestricted) and refactor
(refactor_permission's allowlist) -- not into the local-model minimal/
dynamic toolsets, matching the existing convention of keeping local
models' visible surface small.

Found and fixed a real gap in lex-lang while validating this: `lex
run`'s CLI never wired StdinApprovalSink, so every approval request
was unconditionally refused regardless of what stdin contained --
fixed upstream (lex-lang#807). Verified end to end against the real
dispatch path here after that fix: denying returns "denied by
operator"; approving genuinely proceeds to gh pr create, which then
fails for the expected unrelated reason (head branch same as base) --
confirming the tool only ever runs after a real approval.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… tools

CI's smoke test requires every tool to either invoke a real command it
can safely run unconditionally, or be in the skip list with a reason.
github_pr_create shells to a real `gh pr create` and is now also
approval-gated -- a smoke run in CI has no interactive stdin to answer
that prompt, so it would either hang or (once StdinApprovalSink hits
EOF) deny every time, proving nothing either way. Same shape as
vcs_branch_create/vcs_merge_start/write/edit/bash: covered by its own
CLI-shape check, not run for real here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@alpibrupa
alpibrupa merged commit b8add09 into main Sep 8, 2026
1 check passed
@alpibrupa
alpibrupa deleted the feat/github-pr-tool branch September 8, 2026 15:31
alpibrupa added a commit that referenced this pull request Sep 9, 2026
github_pr_create (#152) got a change as far as "a PR exists". Landing
it always meant a human (or an agent driving `gh` by hand outside this
codebase) polling `gh pr checks`, then running `gh pr merge` once
green -- the exact pattern GitHub Copilot's "Agent Merge" and Codex's
automatic pre-merge approval reviews both shipped this year, and
exactly what a real dogfooding session did by hand for six PRs in a
row before this tool existed.

Approval-gated for the same reason github_pr_create is: merging is
visible to everyone with repo access and cannot be un-merged. The
approval prompt covers the whole conditional intent ("merge this once
checks pass"), asked before the outcome is known -- the same shape as
a human reviewer saying "LGTM, merge when CI is green".

Verifies checks itself (`gh pr checks --watch --fail-fast`) rather
than trusting the caller's own judgment, and refuses to call `gh pr
merge` at all unless that reports success -- a CI failure's own output
becomes the tool's error detail, so the failure can actually be
diagnosed rather than just reported as "didn't merge". `pr` accepts
anything `gh` itself accepts (number, URL, branch name).

Wired into refactor_permission's allowlist and all_tools_for_mode,
same as github_pr_create; added to smoke.lex's skip list (a smoke run
must never trigger a real merge).

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
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.

1 participant