Commit Graph
4 Commits
Author SHA1 Message Date
notplantsandClaude Fable 5 e185cec88c tangled_pr: verify against the pulls list, not a response header; and a tools test suite
THE BUG THAT PROMPTED THIS. tangled_pr.py judged success ONLY by an HX-Redirect header on the POST.
A create that SUCCEEDED but answered without that header read as a failure, so the caller retried
and Tangled grew duplicates — that is exactly how #397, #398 and #399 were filed for one branch. A
response header describes what the server meant to say; it is not the artifact.

Now it checks the artifact, in both directions:
  * BEFORE posting, refuse if an open pull already exists for this source branch, naming it. A
    retry cannot duplicate, whatever the response said. (--allow-duplicate to override.)
  * AFTER posting, confirm against the pulls list: a new pull number that did not exist before,
    whose page names this source branch, IS the success — with or without a redirect header.
  * Failure is reported only when no such pull appeared. A false failure is worse than a loud
    error here, because the caller's remedy is to retry.
Verified live: a dry-run against a branch that already has a pull refuses with rc=3, naming #417.

TWO REAL DEFECTS FOUND BY WRITING THE TESTS.

agents.py shelled out to `pgrep -P` and `ps -o comm=`. Neither is on the agent PATH on this host,
and a missing binary under shell=True returns rc=127 with EMPTY stdout — indistinguishable from
"this process has no children" and "no build is running". So _build_running was ALWAYS False and
the stall detector could reboot an agent mid-build. Both now read /proc directly: no PATH
dependency, and it cannot fail silently in that direction.

That shipped because the unit tests MOCKED pgrep and ps. The fakes stood in for the broken
dependency, so the suite passed on a host where neither tool was reachable and never exercised the
real path. The tests now patch _proc_descendants and _comms — the seams this repo owns. A test that
mocks a dependency proves the mock works.

Also fixed a monkeypatch leak those tests had: restoration used a name derivation that silently
matched nothing, so the patch escaped into another test class and failed an unrelated test — only
in a full run, never when that test ran alone. Now addCleanup, which cannot be ordered wrong.

NEW: tests/test_tools.py, 24 tests over tangled_pr, tangled_pr_close and gateway-domain, with every
HTTP boundary injected so they run offline. Mutation-checked: breaking classify(), the pull-number
regex, the branch match, or the scan bound each turns the suite red. Suite is 93 tests, green, and
order-stable across repeated runs.

README: a "PATH on a NixOS host" section. Every one of ps, pgrep, free, cmp, awk, curl, diff,
strings, nm, getent and ping is INSTALLED here and simply not on the agent PATH, so each reports
"command not found" and reads as a missing package. Documents how to check before concluding a tool
is absent, how to add the system profile, `nix shell` for what is genuinely missing, and the rule
that harness code should not shell out for what the kernel already exposes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3LdmEL7CvCYTNpoBq1kce
2026-08-21 03:50:11 +00:00
notplantsandClaude 300e69d3b3 refactor(secrets): consumers read the store; drop materialized copies
Materializing wrote a second plaintext file per secret, which is the problem the store
was meant to solve: two files drift, and the copy is what ends up committed or grepped.

- tangled_pr / tangled_pr_edit / tangled_repo now read tangled.cookie from the store.
  engine/.tangled-session is deleted; --cookie-file remains as a legacy escape hatch.
- materialize() is replaced by run-time injection that leaves nothing at rest:
    exec-env <group> -- cmd      group as env vars (use this instead of a systemd
                                 EnvironmentFile — same effect, no plaintext on disk)
    with-file <key> -- cmd {}    0600 file in a private tmpdir, removed when cmd exits,
                                 for consumers that insist on a path (ssh -i, a TLS key)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-01 16:58:26 +00:00
notplantsandClaude 26d93c1eda fix(tangled_pr): POST the htmx /pulls/new form, not /pulls/
/pulls/ is 405. The appview's PR form is htmx: it only processes the create when
it sees HX-Request, otherwise it re-renders the page — a 200 that silently creates
nothing. Success comes back as HX-Redirect (…/pulls/<n>), not a 3xx Location, so
the old redirect check never fired. Also send source=branch + the title/body Dirty
flags the form expects, and point the auth-failure hint at the current cookie path.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWTdUq2bsic7JZGqJp3nD6
2026-08-01 16:36:09 +00:00
notplantsandClaude Opus 4.8 071d74f21f feat(tools): tangled_pr.py — file a Tangled PR via the appview OAuth web endpoint
Reusable utility (not project-specific): tangled's appview only indexes pulls created through its
OAuth-authenticated web endpoint POST /{owner}/{repo}/pulls/ (it fetches the knot patch + inserts into
its DB directly); raw com.atproto.repo.createRecord does NOT index. This tool reuses a browser session
cookie (gitignored .tangled-session) to POST target/source branch names. No blob upload, no CSRF.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWTdUq2bsic7JZGqJp3nD6
2026-07-08 05:10:38 +00:00