Repository navigation
ADR-0014: issue sessions run as a Claude Code routine - #171
Conversation
Proposed. Admits anthropics/claude-code-action at v1.0.268, pinned by SHA; runs start only from Hafeok's label or @claude comment on an issue, behind the action's own write-access check; commits are signed with the agent key as Claude (Emil), with Claude Code's attribution off; the OAuth token, not an API key; plan first, revisions, and approval of the newest plan; one repository, 180 minutes, one run per issue, no workflows permission. The job token starts no gates, so Emil opens the pull request and checks the gates ran on its head. Refs #170 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYRYCm8wULG3wF2NvgE
Triggered by issues: labeled and issue_comment: created, with the job's if: requiring Hafeok as the label's sender or the comment's author, a comment on an issue, and the @claude prefix. Checkout and setup-dotnet at the SHAs gates.yml pins; the action at c0949c50 (v1.0.268) in tag mode, with the run's rules appended to its system prompt. Refs #170 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYRYCm8wULG3wF2NvgE
eng/gates/claude-workflow.sh fails when claude.yml departs from ADR-0014 D1 to D7. selftest plants an action pinned to a tag, a SHA with no version, workflows: write, id-token: write, a job's own permissions, each missing actor check, a check present only in a comment, pull request comments admitted, a pull_request_target trigger, allowed_bots, a prompt input, attribution left on, and no timeout. Each fails with its finding; the clean file passes. Refs #170 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYRYCm8wULG3wF2NvgE
How to start a run (the label, revisions, @claude approved, opening the pull request and checking its gates), what a run may do, untrusted issue text, and which issues must not get the label. Refs #170 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYRYCm8wULG3wF2NvgE
The descriptor for ADR-0014 and claude.yml, with accepted-by left for Emil, and the host's version to match. If #169 lands first, this descriptor and the version are renumbered, as the ADR is. Refs #170 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYRYCm8wULG3wF2NvgE
The brief and the approval, verbatim; what was read, at the action's pinned commit; what the session found in the action's source and in Claude Code's settings reference; the commits with GitHub's verification under the Claude (Emil) identity; every commit checked on its own; the build, tests and smoke at the head; the plants' findings; and the deviations from the plan, with their reasons. Refs #170 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYRYCm8wULG3wF2NvgE
…i's issue sessions); Round 31 Round 31, recorded verbatim in the 2026-11-07 session's rulings.md, adds two open items to sdlc.md §19, as open items only. Neither rules on its substance, and DDD-dec-115 is not amended. - O-8: no act accepts o1b's plan before work starts; o10 accepts only the candidate. product-cli's issue sessions have one (its ADR-0014 D5, #171, Proposed there). The proposal, a plan-acceptance act between o1b and o4 whose verdict is in o4's ground, stays a proposal. The row says where it would sit in the occurrence record (§13) and in the meters (§14, §18). Closes it: Emil. - O-9: product-cli's issue sessions (label by one maintainer, plan approval, maintainer fast-forward, a record per session) against the SDLC (allocation label checked against the map, approve <head sha>, the App's fast-forward, a record per occurrence). product-cli adopts the SDLC or records its departures. Closes it: Emil, then product-cli. Neither item was already covered. O-8 and O-9 are §19's next free numbers; pdlc.md §15's own O-8 and O-9 are a separate list. Refs #61 Refs mindovermachine-dev/product-cli#169 Refs mindovermachine-dev/product-cli#171 Basis: DDD-dec-115 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYPPHBsK3xfFSuALfiQ
…i's issue sessions); Round 31 Round 31, recorded verbatim in the 2026-11-07 session's rulings.md, adds two open items to sdlc.md §19, as open items only. Neither rules on its substance, and DDD-dec-115 is not amended. - O-8: no act accepts o1b's plan before work starts; o10 accepts only the candidate. product-cli's issue sessions have one (its ADR-0014 D5, #171, Proposed there). The proposal, a plan-acceptance act between o1b and o4 whose verdict is in o4's ground, stays a proposal. The row says where it would sit in the occurrence record (§13) and in the meters (§14, §18). Closes it: Emil. - O-9: product-cli's issue sessions (label by one maintainer, plan approval, maintainer fast-forward, a record per session) against the SDLC (allocation label checked against the map, approve <head sha>, the App's fast-forward, a record per occurrence). product-cli adopts the SDLC or records its departures. Closes it: Emil, then product-cli. Neither item was already covered. O-8 and O-9 are §19's next free numbers; pdlc.md §15's own O-8 and O-9 are a separate list. Refs #61 Refs mindovermachine-dev/product-cli#169 Refs mindovermachine-dev/product-cli#171 Basis: DDD-dec-115 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYPPHBsK3xfFSuALfiQ
|
Change of design before I merge: option 2. A run no longer happens on a GitHub runner. It is a Claude Code routine in my cloud environment, which already holds the agent's signing key and the login. GitHub only starts it, through the routine's API trigger: Please revise and post a new plan on #170 before changing the PR.
What I do after the revised plan is approved: create the routine in claude.ai/code/routines (repository product-cli, my key-holding environment, an API trigger), generate its token, and add Generated by Claude Code |
…utine Still proposed, still 0014, renamed to ADR-0014-routine-issue-sessions. A routine in Emil's key-holding environment does the work; a workflow only starts it through the routine's API trigger, with two secrets. The boundary between Emil and his sessions is the typed-by-hand check, performed_via_github_app == null, recorded with its evidence on #170: it replaces the login check, since sessions comment as Hafeok through the claude app. The fire API's limits, its lack of idempotency and how that is contained; the runner design kept as the rejected alternative, with its six findings; the scheduled-task signing claim corrected. Refs #170 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYRYCm8wULG3wF2NvgE
Pasted whole into the routine. The payload only names the issue; the session checks the start against GitHub over REST, takes instructions only from comments and label events Hafeok made by hand, never starts a comment with @claude, plans first, implements only the newest plan an approval names, stops on a duplicate start, and follows AGENTS.md. Refs #170 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYRYCm8wULG3wF2NvgE
claude.yml becomes one job with one curl step to the routine's fire URL: the if: unchanged but for performed_via_github_app == null on the comment branch, contents: read, a 5-minute timeout, the per-issue concurrency group, one attempt with no retry, the payload built with jq from env:, and the response kept out of the public log. claude-workflow.sh now reads the workflow and the prompt, and fails on any departure from ADR-0014 D2 to D7 or on a disagreement between the two on the actor, label, prefixes and rule sections. selftest plants the old action, a third secret, an unpinned checkout, a second curl, a hard-coded URL, a retry, contents: write, each missing actor check, a check present only in a comment, an app comment admitted, an expression in run:, comment text in the payload, a pull_request_target trigger, no timeout, another actor in the prompt, no Plan first section, and a start check without the app check. Refs #170 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYRYCm8wULG3wF2NvgE
How a session starts and is approved, typed by hand; where its prompt lives and how it changes; what a session may do; untrusted text; and which issues must not get the label. The gates-table row follows the rewritten check. Refs #170 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYRYCm8wULG3wF2NvgE
The title, summary and basis follow ADR-0014 as rewritten; accepted-by is still left for Emil, and the version is unchanged. Refs #170 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYRYCm8wULG3wF2NvgE
The design change, the revised plan and the approval, verbatim; what was read of the fire API and the routines page; the evidence for the typed-by-hand boundary; the commits with GitHub's verification; every commit checked on its own; the fire step run against a stand-in; the plants' findings; and the deviations from the revised plan. Refs #170 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYRYCm8wULG3wF2NvgE
|
Approved the revised plan (6790423943) with the answers given; implemented in #171 |
The two secrets move from the repository to the GitHub environment claude-routine, whose deployment branches are limited to main, so no workflow on another branch can read the fire token. Refs #170 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYRYCm8wULG3wF2NvgE
The job must declare the environment, by that name, in its short form. selftest plants a job without it, one naming another environment, and one naming it in the object form; each fails with the finding. Refs #170 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYRYCm8wULG3wF2NvgE
…utine Deployment branches limited to main and no required reviewers; why a repository secret was not enough; why issue and comment starts are still admitted (their workflows run from main); the gate's new check; and the environment in Emil's setup, created before the first start. Refs #170 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYRYCm8wULG3wF2NvgE
Where the two secrets live, the gates-table row's new failure, and the setup step that creates claude-routine. Refs #170 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYRYCm8wULG3wF2NvgE
The request verbatim; what the session found about default-branch runs and environments created on first use; the commits with GitHub's verification; the checks; and the two extra plants. Refs #170 Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk> Claude-Session: https://claude.ai/code/session_01NbWSYRYCm8wULG3wF2NvgE
|
Approved at b49df86 |
Refs #170. This PR implements the revised plan for option 2 (design change), as Emil approved it with three answers and one change. It also adds the bridge's GitHub environment, requested before landing. The record quotes each request verbatim.
The first six commits (
c676b48to621fa59) are the runner design as first approved. The rework sits on top, and no history was rewritten. This PR changes a workflow, so it was written in a normal key-holding session.What changes
ADR-0014-routine-issue-sessions.md).ROUTINE_FIRE_URLandROUTINE_FIRE_TOKEN. They live in the GitHub environmentclaude-routine, whose deployment branches are limited tomain, never as repository secrets, so no workflow on another branch can read the token. Issue and comment starts run frommain, so they are admitted.performed_via_github_app.slug == "claude"; comments Emil types carry none. The evidence on Issue sessions: the Claude Code action, started by the claude label (new ADR) #170 is recorded. Both the bridge'sif:and the session's start check requireperformed_via_github_app == null, with the no-@clauderule as a second line of defence.429withRetry-After. There is no idempotency; duplicates are contained by curl never retrying, the per-issue concurrency group, and the session's own duplicate check..github/workflows/claude.yml, the bridge: one job inenvironment: claude-routine, withcontents: readand a 5-minute timeout. It sends onecurlwith no retry and noanthropic-betaheader, builds the payload withjqfromenv:, and keeps the response out of the public log.eng/routines/issue-session.md: the routine's prompt, version 1, which Emil pastes into the routine.claude-workflow, the gate, now reads both the workflow and the prompt. It fails when they disagree on the actor, label, prefixes or rule sections, or when the job lacks exactlyenvironment: claude-routine.selftestplants 21 cases.AGENTS.md: how a session starts and is approved by hand, where its prompt and secrets live, and which issues must not get the label.releases/v0.8.0-preview.3.yaml, withaccepted-byleft for Emil.docs/traceability/2026-11-06-issue-170-issue-sessions.md, in three parts.Checks
commits,adr-shape,adr-text,no-toolchain,claude-workflowandselftestall pass.645ad60: the build has 0 warnings and all 43 tests pass, and the linux-x64 AOT publish and smoke test pass. No source changed after that commit.200it sends exactly the three headers and logs only the issue and event; on429it fails and prints the API's error. A value with a quote, a backslash and a newline arrives intact in valid JSON.Claude (Emil), attributed toHafeok.For Emil
claude-routinebefore merging: deployment branches limited tomain, no required reviewers. A job that names a missing environment creates it with no rules.The first test run confirms that
performed_via_github_appreaches theif:.🤖 Generated with Claude Code
https://claude.ai/code/session_01NbWSYRYCm8wULG3wF2NvgE