Repo: factory · recipe: agent:single
The defect
Factory has two GitHub write paths with two different identities, and only one of them is deliberate.
PR creation goes through the app. src/mount/relayfile-github-connection-write.ts:92 writes author: 'app', and the comment at :107 says so explicitly:
"The workspace adapter contract explicitly requests app authorship. The concrete bot login is installation-specific and is not included in every acknowledgement receipt, so retain the stable identity label."
The babysitter shells out to gh. src/github/standalone-babysitter.ts:28 and :99. Its own target type admits it: source: 'mount' | 'gh' | 'mount+gh'. Same for src/github/merge-gate.ts:178 and src/intake/notion.ts:784.
gh authenticates as whatever local user is logged in — here a gho_ OAuth token for a human account. So every comment, commit and label the babysitter writes is attributed to a person, while Factory's own PRs are attributed to the app. One product, two audit trails, and the difference is invisible from the outside until you read the source.
This matters more now than it did yesterday: the babysitter is about to run against a large number of PRs that Factory did not create.
Why the shell-out is wrong beyond the identity
Standing rule from the principal: always use the SDK surface; do not shell out to another product's CLI. Shelling out:
- makes a CLI binary a runtime dependency of every host Factory runs on;
- couples two release trains across a process boundary with no type checking —
relayfile mount recently rejected --workspace and --local-dir, flags its own help text advertised;
- launders errors through exit codes and stdout parsing rather than typed results;
- silently inherits whatever identity happens to be logged in, which is exactly the bug above.
The connection Factory needs already exists and is already used for PR creation.
What done looks like
- Babysitter GitHub writes go through the Relayfile GitHub connection, with
author: 'app', matching PR creation. Same for merge-gate.ts and the gh call in intake/notion.ts.
source: 'gh' stops being a supported provenance for babysitter targets, or its meaning is documented and deliberately retained with a reason.
- Identity is asserted, not assumed. A test that proves a babysitter comment is authored by the app and not by whoever ran the process. Do not accept "it worked" as evidence — read back the author on a real write.
- If any path genuinely cannot go through the connection today, say so explicitly with the reason rather than leaving a silent
gh fallback. A documented limitation beats an invisible one.
Constraints
- Do not merge. The merge gate belongs to the principal.
- Do not change PR-creation authorship; it is already correct.
- Report validation by exit code, not by output — a check that cannot fail is not a check.
Repo:
factory· recipe:agent:singleThe defect
Factory has two GitHub write paths with two different identities, and only one of them is deliberate.
PR creation goes through the app.
src/mount/relayfile-github-connection-write.ts:92writesauthor: 'app', and the comment at:107says so explicitly:The babysitter shells out to
gh.src/github/standalone-babysitter.ts:28and:99. Its own target type admits it:source: 'mount' | 'gh' | 'mount+gh'. Same forsrc/github/merge-gate.ts:178andsrc/intake/notion.ts:784.ghauthenticates as whatever local user is logged in — here agho_OAuth token for a human account. So every comment, commit and label the babysitter writes is attributed to a person, while Factory's own PRs are attributed to the app. One product, two audit trails, and the difference is invisible from the outside until you read the source.This matters more now than it did yesterday: the babysitter is about to run against a large number of PRs that Factory did not create.
Why the shell-out is wrong beyond the identity
Standing rule from the principal: always use the SDK surface; do not shell out to another product's CLI. Shelling out:
relayfile mountrecently rejected--workspaceand--local-dir, flags its own help text advertised;The connection Factory needs already exists and is already used for PR creation.
What done looks like
author: 'app', matching PR creation. Same formerge-gate.tsand theghcall inintake/notion.ts.source: 'gh'stops being a supported provenance for babysitter targets, or its meaning is documented and deliberately retained with a reason.ghfallback. A documented limitation beats an invisible one.Constraints