Authenticate the pipeline container by login or by token - #853
Closed
Jack-Edwards wants to merge 45 commits into
Closed
Authenticate the pipeline container by login or by token#853Jack-Edwards wants to merge 45 commits into
Jack-Edwards wants to merge 45 commits into
Conversation
The pipeline agents run unattended, so they run in a container with no access to the host filesystem or host credentials. The image carries the toolchain a Crypter build needs -- .NET 10, pnpm pinned to the version CI uses, wasm-tools, dotnet-ef -- plus git, gh, and the Claude Code CLI. Docker is deliberately absent. Handing the container a Docker socket would hand back the host, so Crypter.Test cannot run inside the loop; the tests run in CI once the pull request exists. The agents run as uid 1001 rather than reusing the base image's uid 1000. That account belongs to the sudo group, and while sudo is not installed today, a derived image that installed it would silently grant the agents root. The workflow publishes to GHCR on pushes to stable that touch .devcontainer, and needs a DEVCONTAINER_IMAGE_NAME repository variable to exist. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Each stage of the pipeline runs as its own subagent so that it starts from a clean context. A stage that inherits the reasoning behind the work it is checking tends to ratify it instead of examining it. Tool grants do the enforcing. The conformance auditor and the reviewer cannot write code, so a deviation or a defect they find has to be reported rather than quietly repaired. The implementer is reused for the initial build, for accepted review findings, and for CI fixes, so there is no fourth definition. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The skill drives a requirement from plan to a published pull request: sync and branch, plan, implement, open the pull request as a draft, audit and review in parallel, remediate, then publish and hold the pull request against CI. The plan is the one place it stops for a human. The CI loop lives here rather than in the publisher agent. A subagent cannot invoke another subagent, so the publisher runs a single attempt and reports what CI said, and the skill counts attempts and calls the implementer between them. That also means each attempt reads the current state of the checks instead of reasoning from its own earlier diagnosis. Three attempts, then it hands back. Everything happens on a fork, so nothing the agents run can reach this repository. Run state under .claude/pipeline is ignored. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Docker creates a mount point the image does not contain as root, so the volume mounted at /home/agent/.claude arrived read-only to the agent user and Claude Code could not write its settings or credentials. /work already avoided this by being created in the image; the home directory now does the same. CRYPTER_GIT_NAME and CRYPTER_GIT_EMAIL were read by the clone script but never passed through containerEnv, so every commit was authored as the fallback identity. CRYPTER_WORKSPACE could never diverge from workspaceFolder without breaking the container, so it is now a constant.
Crypter.Test's project graph does not include Crypter.Web or Crypter.Test.Web, so building it alone left changes to either uncompiled until CI, which builds the whole solution and runs both test projects.
Checks run on draft pull requests, so the round of checks starts when the pull request is created at stage 3 and again on every push. `gh pr ready` fired nothing, because ready_for_review is not among the events unit-test.yml and codeql-analysis.yml listen for. Stage 7 waited on a run that would never come, and on a clean run — where triage accepts nothing and no remediation is pushed — it reported the fork's workflows as disabled and stopped. Stage 7 now resolves the branch's head commit and watches the round of checks for it, and the pull request stays a draft for the user to publish once they have reviewed it. `publisher` is renamed `ci-watcher` to match. The skill also pushes, at stage 3 and after each implementer stage. Nothing cancels a superseded round of checks, so a push per commit would spend several rounds to learn what the last one says.
Both agents have Bash, so "you cannot edit code" was a rule dressed up as a capability boundary. An agent that finds the claim false has no reason to treat the rest of the instruction as binding either. Reviewer findings are checked at triage, in a context that did not write them. "Verify before you report" read as licence to run code and confirm its own finding, which is the one check that context cannot perform honestly. Grounding a finding means reading the code paths, not executing them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nothing in the repository said what CRYPTER_FORK or CRYPTER_FORK_TOKEN were, what scopes the token needs, or that GitHub disables workflows on a new fork until you enable them by hand. That last one surfaces as the pipeline reporting no CI run ever appeared, which reads like a bug. The devcontainer image now takes its owner from the environment, still defaulting to the org's published image. The build workflow tags from the repository owner, so a fork that changes the Dockerfile publishes to its own namespace and previously had no way to point the container at it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A broken Dockerfile previously surfaced after merge, when the push workflow ran on stable, or not until someone rebuilt their container. The gate is a new output on detect-code-changes rather than an on.pull_request.paths filter. A workflow filtered out by paths reports no check at all, which leaves a required check pending forever; a job skipped by an if condition still reports. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There are five, not four, and all of them gate on detect-code-changes rather than only the two image builds. An agent expecting four checks reads the fifth as something having gone wrong. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`gh pr checks` reports job names, not workflow names, so the previous list matched nothing the agent would actually see. It also missed build-and-test-web entirely: unit-test.yml runs two jobs, and that is the one covering Crypter.Test.Web, which sits outside Crypter.Test's project graph and so cannot be built inside the container. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Omitting `tools` grants everything, including Agent. An implementer that can spawn its own subagents undercuts the premise the pipeline is built on, which is that each stage starts from a context that did not write the work it is looking at. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
build-and-push-devcontainer.yml filters itself into its own push trigger but was absent from the pull request gate, so edits to it merged without ever building the image they publish. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
API_IMAGE_NAME and WEB_IMAGE_NAME are variables because deployments vary. The devcontainer image has one consumer, devcontainer.json, which names it literally, so the only value DEVCONTAINER_IMAGE_NAME could hold was the one now in the workflow. Unset it produced a malformed reference; set wrong it published an image nothing would pull. Setting up a fork is one step shorter as a result. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The setup document covered the fork, the token and enabling Actions, then stopped short of the one credential the pipeline cannot run without. The crypter-pipeline-claude volume exists to persist it and nothing said so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The container was described only by devcontainer.json, which needs a separate tool to interpret it and left the setup documentation with no instruction for starting anything. Compose is already a dependency of this repository, so the container can be described the way the application stack is, with its values in a tracked .env that reads like the root one. devcontainer.json goes rather than sitting alongside, so there is one definition to keep correct. It is a separate Compose project rather than a service in the root file. The agents only ever run dotnet build, git and gh, so they need nothing from the api, web or db services, and sharing a file would put the container on the application stack's network and leave a root `compose down` unable to remove a network still in use. crypter-clone-fork now runs on every start rather than once at creation. It already leaves an existing workspace alone and only refetches, so work in progress still survives a restart. The image bakes in clone-fork.sh, so its corrected guard messages need an image rebuild to take effect.
…peline Add an agentic pipeline that runs in a devcontainer
The publish workflow pushed to GHCR as soon as anything under .devcontainer/ landed on stable. It now runs in the devcontainer environment, so a reviewer approves the run before the image moves. Publishing from a fork is not supported, so its instructions are gone along with CRYPTER_DEVCONTAINER_OWNER, which existed only to point the container at a fork's image. Testing an image change is now a local build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A build section makes Compose build the image when it is absent locally, without ever contacting the registry, so every first start built the image from scratch. Declaring the pull policy restores the pull and leaves `compose build` available for image work. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ainer-publish Gate the devcontainer image publish and pull it instead of building
CRYPTER_FORK_TOKEN is a live GitHub personal access token, and while the file
was tracked the only thing keeping a filled-in token out of a commit was the
contributor noticing what they staged. Git now ignores the file, so it cannot be
committed by accident, and .devcontainer/.env.example carries the four keys to
copy from.
Anyone who already has a filled-in .devcontainer/.env keeps it: the file stays on
disk here, only its tracking stops. Pulling this commit with a modified copy
aborts the merge, and untracking alone is not enough — git then refuses to remove
what has become an untracked file. Untrack it and commit that removal before
pulling:
git rm --cached .devcontainer/.env
git commit -m "Untrack devcontainer env"
git pull
The merge then succeeds, the file and its token survive, and the tree is clean.
The ignore rule is anchored to .devcontainer, so the root .env stays tracked.
A contributor who already has a filled-in .devcontainer/.env hits a modify/delete conflict on the pull, and git's printed resolution, git rm .devcontainer/.env, deletes their live token. The doc now gives the git rm --cached sequence to run before pulling instead. The same section claimed the file is ignored by git so the token cannot be committed by accident. That holds only on a fresh checkout: .gitignore does nothing for an already-tracked path, so any branch cut before this change still commits the token as before. The claim is now scoped to fresh checkouts.
CRYPTER_GIT_NAME and CRYPTER_GIT_EMAIL fell back to a generic name and a noreply address, so an unconfigured container pushed commits to a contributor's fork under an identity belonging to nobody. Both are now guarded in clone-fork.sh the same way CRYPTER_FORK and GH_TOKEN already were, and Compose passes them through unsubstituted. The setup document's notes on moving off the tracked file described a migration rather than the setup as it stands, so they are gone along with the qualifications about branches cut before the file stopped being tracked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stop tracking .devcontainer/.env and ship a template instead
The plan now comes from /crypter-plan-author, a skill an interactive session runs on the host, where the web, the user's tooling and the user are all reachable. Compose mounts .claude/plans read-only at /plans, so the container reads the plan where it was written and keeps its own run state in the workspace. /pipeline takes a run id and a branch and starts at the sync, with the host session driving it once the plan is approved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The container ran with a fork-scoped PAT and unrestricted egress, since Claude Code needs the Anthropic API. Rather than harden egress, the credential is gone: the workspace is now an anonymous clone of the org repository whose only remote has no push url, so the agents read public code and commit locally. Pushing, opening the pull request and the CI loop move to the host, where the session already has GitHub access. /crypter-publish fetches the branch out of the container over git's ext transport, pushes it, opens the draft pull request, and runs at most three fix attempts, handing each failure to /pipeline-fix through the read-only plans mount. CRYPTER_FORK and CRYPTER_FORK_TOKEN leave .env, and the image drops the GitHub CLI, which has nothing left to authenticate with. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One skill drafted a plan and launched a container, and another implemented, reviewed, triaged and remediated. Neither name described what it did, and neither part could be used alone. The work now sits in small skills that compose: crypter-plan, crypter-implement, crypter-examine, crypter-remediate and crypter-publish. crypter-change carries a requirement through all of them and owns the CI loop, where the plan, the findings and every previous attempt are already in context. crypter-review runs the same lenses against a pull request that already exists, including one raised by someone else, and reports without posting anything. Findings become artifacts on the host. A second mount carries .claude/runs into the container, and each reviewing agent writes its own file there, so what was found and what was rejected can be read without docker exec. The orchestrators create those directories because the container's agent is uid 1001 and host files are uid 1000. The reviewer's lens table grows from one row to correctness, maintainability, testability and security. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
crypter-examine claimed the branch it reviewed, so it failed whenever anything else already held that ref. It only reads, and a detached worktree reads just as well. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pipeline reviewed its own branch. Acting on what it raised: crypter-publish becomes crypter-open-pull-request, since the skill only ever opens a draft and taking a pull request out of draft is the user's call. ci-watcher loses the fork parameter it was never given and resolves the repository from the path it already has, and reads the branch's version of a file rather than whatever the working tree is checked out at. crypter-review forces its fetch refspec, so a pull request that was rebased or amended can be reviewed again. The mount table named the run-id directory as the mount source; the mounts are the parents. The pre-flight check tested the parent of /runs, which uid 1001 cannot write to by design, so it now tests the run directory the caller just created. The launch steps create both mount sources, because Docker creates a missing bind-mount source as root. The documentation credited a fork-scoped token with bounding the container and claimed it holds no credential; the Claude Code credential is still there, so it says GitHub credential and names the container as a trust boundary. The orchestrators state that they run from the main checkout, which is where the mounts resolve.
The agents run unattended, which is the whole reason permissions had to be resolved without a prompt. Auto mode does that without handing them the bypass, and the unprivileged user in the image stands on its own merits rather than on what the bypass refuses to do as root.
.claude/runs is a writable mount into the pipeline container, and /crypter-review runs agents over diffs written by people outside this project. A symlink left in that directory resolves on the host side, so reading a findings file can read any file the user can, and writing triage.md can overwrite one. The hook blocks a path inside the project that resolves outside it. Paths that point outside to begin with are untouched, since asking for one is deliberate. It is written in Node because the build already depends on it, so it runs where a shell script would not.
A review that lands only on the reviewer's disk asks the reviewer to retype it. crypter-review now triages what the lenses raised and posts a single review that comments, with a line comment for each finding that lands inside the diff and the rest in the body. It never approves and never requests changes. The pull request may belong to someone else, and either verdict is the user's to give. Triage comes first, and it is written to triage.md, so the findings that reach the author are the ones that survived a read against the code, and so a later remediation run has something to start from.
A finding on a pull request is a claim, and acting on a wrong one means changing working code. crypter-triage-review gives each finding its own verifier with the code and nothing else to judge it by, so no agent ever rules on a finding it raised. What holds becomes a commit. What does not gets a reply on its thread carrying the evidence, so the person who raised it can answer. What the verifier cannot settle goes to the user untouched. The commits go back to the pull request's own branch, which is only possible when it comes from a repository the session can push to. Otherwise the replies stand on their own and the author does the fixing.
The skills are visible wherever the repository is checked out, and four of them only work inside the pipeline container, where /work/Crypter, /plans and /runs exist. The prefix says which is which, so the skills a user invokes are the ones without it.
The two skills a session runs on behalf of an orchestrator now say so in their names. crypter-step- rather than crypter-change-step- because open-pull-request is a step of crypter-triage-review as well. What is left unprefixed is what a user invokes: crypter-change, crypter-review, crypter-triage-review.
Restructure the agentic pipeline into task skills and orchestrators
The pipeline skills assumed origin was a personal fork and upstream the org repository. This checkout has one remote, origin, pointing at the org, so crypter-step-plan failed on its first command and the push step had no upstream/stable to sync a fork from. Branches now go to the org repository the way every branch before the pipeline did, which removes the fork sync push and the second pull request that had to be opened by hand afterwards. crypter-step-open-pull-request checks origin before pushing, since a checkout wired up differently sends the branch somewhere the pull request will not find it. Container-side skills keep the upstream remote name. That is the name the container's own clone gives its single read-only remote, and it is unaffected. Co-authored-by: n <e@f> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* Watch CI with gh instead of a polling loop The CI watcher was told to poll for check runs every thirty seconds. A foreground sleep does not run in this harness and the agent has no timer, so the interval it was asked for was one it could not take. The documented way out was gh pr checks --watch, and gh was not installed, so that path was dead too. gh is now a hard dependency rather than an alternative offered where it happens to exist. Watching is a single blocking --watch call, and the exit code distinguishes a failure from a watch cut short before the round finished, which would otherwise burn a fix attempt on a run that was merely still going. Failure diagnosis reads the failing job's log through gh run view --log-failed rather than scraping check run annotations. Annotations carry compiler diagnostics well and test failures poorly, and the report this agent writes is the only thing the implementer gets. gh carries its own credential, separate from the MCP server's, so the agent checks gh auth status first and names the missing login rather than failing part way through with an authentication error. crypter-triage-review named gh pr comment as the only way to answer a finding with no thread, which was unreachable for the same reason. It now names the MCP equivalent alongside it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Treat a gh authentication failure as a setup problem An unauthenticated gh pr checks exits 4, which was outside the set of exit codes the watcher was given. Left undocumented it reads as a non-zero exit and therefore as a failing build, which sends the implementer looking for a defect that is not there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Move the CI watcher's mechanics into a script Waiting, reading exit codes, finding failing runs and cutting a log down to the part that explains itself are all deterministic, and they were written as prose for an agent to follow. Prose cannot be run, so the mistakes in it only surfaced when a run went wrong: a polling interval the harness cannot take, an exit code that reads as a failing build when it means nobody logged in, an instruction to read the tail of a log whose last fifty lines are cleanup. ci-status.sh does that half and can be tested. Exit codes separate a CI failure from the three setup problems that otherwise look like one. On a failure it prints the window of log ending at the runner's ##[error] marker, which turns a 1100-line dump into roughly sixty lines containing the cause. The agent keeps the half that needs judgement: reading back from the symptom to the cause, reading the branch's version of the code the failure names, deciding whether it is a defect, a wrong test or the plan itself being wrong. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Keep gh behind the script rather than in agent prose Two skills offered a bare gh call as an alternative to the MCP server for posting a review and commenting on a pull request. The MCP server does both, so those were discretionary CLI invocations with nothing to recommend them. The watcher's remaining direct call was fetching a fuller copy of a log the script had already downloaded. The script now keeps each failing job's log and prints its path, so reading further means opening a file rather than going back to the network. Nothing outside ci-status.sh invokes gh now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: n <e@f> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* Build and review in per-run container workspaces The container kept a long-lived clone of the org repository in a named volume. Nothing ever advanced its working tree, so the pipeline's own skills and agents were frozen at whatever commit the volume was created with. The rename in #842 made that visible: the host began invoking crypter-devcontainer-implement while the container still only knew crypter-implement, and both orchestrators broke. Workspaces are now created per run at /work/{run-id} and deleted when the run ends, cloned from a read-only mount of the host's .git. A copy of the code no longer outlives the run that made it, so it cannot drift. The mount being read-only is what makes sharing history safe: the container reads commits and cannot move a ref or add an object. The host working tree is not mounted, so uncommitted work stays invisible to the agents. The orchestrator owns the lifecycle and the container skills assume a workspace exists, mirroring how /runs already works. This also retires the worktree add/remove pairing in every container skill, along with the stale worktree a crashed run used to leave behind. The image is built locally instead of pulled from GHCR. It carries tooling and no source, so a change to workspace.sh or the Dockerfile is a rebuild rather than a publish waiting on an approval. NuGet and pnpm caches move to named volumes. Workspaces are ephemeral, so without them every run would restore from nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Stop publishing the devcontainer image The image is built locally and carries tooling rather than source, so nothing consumes the published copy. Publishing it on every merge to stable left a registry image nobody pulled and an approval gate on changes that only ever affect the machine making them. pr-build-devcontainer stays. It pushes nothing and catches a Dockerfile that does not build, which is the only part of the publish path that was earning its keep. The devcontainer environment on the repository has no jobs left referencing it and can be deleted from the repository settings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: n <e@f> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The skills open at `docker exec crypter-pipeline` and assume the container is running and belongs to this checkout. Neither holds by itself. The Compose mounts are relative paths, so they resolve against whichever checkout launched the container, and a second checkout finds a container by name whose /runs writes land somewhere it never looks and whose /host-git is a different history. The trap is that an exited container looks repairable. `docker start` reuses the mounts and image fixed at creation, so it brings the wrong container back and the run fails two steps later at workspace creation, reading as a missing executable rather than as the wrong container. crypter-review and crypter-triage-review gain the preflight crypter-change already had, and all three now also check the image carries crypter-workspace, which the mount checks alone let through. The checks are `test` rather than `command -v` because docker exec runs a binary and not a shell, so a builtin exits 127 either way. The remedy is `up -d --build`, not `--force-recreate`, which recreates against new mounts but keeps a stale image. It takes over the one crypter-pipeline on the machine, so the skills ask before running it. Co-authored-by: n <e@f> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
crypter-review reviewed every pull request against stable. A release pull request targets main, so the diff it handed the lenses was stable against itself: empty. Four lenses read nothing and reported nothing wrong, which is indistinguishable from a clean review. The skill now takes the base branch from the pull request and passes it down, and crypter-workspace takes it as --base, defaulting to stable for work built here. A base that does not resolve is caught before the clone, so a corrected retry is not refused by the half-built workspace of the failed one. The workspace's second name for the org repository goes with it. A clone maps the source's local branches into origin/*, which is what upstream/stable existed to work around; pointing remote.origin.fetch at the host's remote-tracking refs gets the same guarantee under one name, and keeps it against a later bare fetch. Co-authored-by: n <e@f> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Every lens was handed the same list of Crypter conventions while also being told to stay inside its lens, so the security lens was primed to report a missing Async suffix and forbidden from reporting it in the same breath. Which way a reviewer resolved that was left to chance. The conventions are now split by what goes wrong when they are broken, and each half lives in the lens brief that can judge the consequence: monads, sync IO, constructors, enums and missing migrations under correctness; history-narrating comments and Async naming under maintainability; validated primitives under security. Testability claims none, rather than being given one to justify the symmetry. That moves them out of the shared agent definition and into the prompt, where the skill already said the lens belongs. The briefs must now be passed in full for the conventions to be reviewed at all. Co-authored-by: n <e@f> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The host skill read every lens finding against the code to decide what to post, which put a judgement about code in the session that has no lens discipline and no worktree. In practice it drifted further than that: with an examine run that produced nothing, reading the diff and forming findings directly looked like the reasonable way to fill the gap. Findings now go to /crypter-devcontainer-verify, which already rules on findings from anywhere else and already rejects preferences and findings about untouched code. It grows a second input shape for this: given the lenses' own directory it assigns ids per lens rather than expecting a collected report, so the host hands over a path and reads nothing until verdicts exist. Only findings that hold reach the pull request. One that a verifier ruled against is the pipeline checking itself, and it stays in .claude/runs where a later pass can read it, rather than costing the author a read to reach a conclusion the verifier already reached. Both container calls exit 0 whichever way they go, so each step now checks what was written instead of trusting the status, and distinguishes an empty result that means success from one that means the run never happened. Co-authored-by: n <e@f> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…#852) The pipeline container was welded to one checkout. Its mounts are relative paths resolved at creation, and both the container and Compose project names were hardcoded, so a second checkout running the pipeline reached the first one's container: findings written under a repository nobody was looking at, and workspaces cloned from a history nobody was reviewing. Workspaces now clone from CRYPTER_REPO_URL rather than from a read-only mount of the host's .git. A run sees the branch as the repository holds it, so the launching checkout no longer decides what gets built or reviewed, and a stale or unfetched host is no longer a way to start a run on the wrong base. Pull request heads come from refs/pull/{n}/head directly, which removes the host-side fetch that staged them. The origin/* fixup in workspace.sh goes with it: it existed only because cloning a checkout maps that checkout's local branches into origin/*, which cloning the real remote does not. Dropping the mount also makes a git worktree a usable checkout for the pipeline. Its .git is a file rather than a directory, so mounting it gave git clone something it could not read. Containers are now named crypter-pipeline-{slug}-{hash}, derived by pipeline.sh from the checkout it sits in. A checkout resolves only to its own name, so which container an orchestrator reaches is settled by where it runs rather than by probing mounts, and recreating one leaves the others alone. The three volumes stay shared machine-wide, so Claude Code is still authenticated once and the caches warmed once; they are external because a Compose-owned volume refuses to mount into a second project. Compose requires the container name rather than defaulting it, so invoking docker compose against the file directly now fails instead of quietly creating a container whose identity says nothing about its mounts. The container needs outbound network for source as well as for the API, so the pipeline no longer works offline. It clones anonymously over https and holds no GitHub credential; branches still leave over the ext:: transport and are pushed from the host. Co-authored-by: n <e@f> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Claude Code in the container authenticates from an interactive login held in the crypter-pipeline-claude volume, shared by every checkout on the machine. That login lapses on its own schedule, and renewing it meant knowing to open a shell in the container and run `claude` there. `pipeline.sh login` makes that a command. CLAUDE_CODE_OAUTH_TOKEN is an alternative for a machine that would rather configure the credential than open a browser: Compose reads it into the container's environment, where Claude Code uses it in place of the stored login. Replacing an expired token means editing .env and running `up` again, since the environment is fixed when the container is created, so the stored login remains the better default. `up` warns when neither is set rather than refusing, because either one on its own is enough. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Jack-Edwards
force-pushed
the
chore/pipeline-oauth-token
branch
from
August 7, 2026 00:50
b7587cc to
40c6059
Compare
Member
Author
|
Closing unmerged. The development pipeline now lives in Crypter-File-Transfer/Claude-Development-Pipeline, and this branch's changes went across with it —
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Claude Code in the pipeline container authenticates from an interactive login held in the
crypter-pipeline-claudevolume. Renewing it when it lapses meant knowing to open a shell in the container and runclaudethere.pipeline.sh loginmakes that a command.CLAUDE_CODE_OAUTH_TOKENin.devcontainer/.envis an alternative for a machine that would rather configure the credential than open a browser; Compose reads it into the container's environment, where Claude Code uses it in place of the stored login.Nothing is required of you after merging — an existing login keeps working.
upwarns when neither a token nor a login is configured, rather than refusing.🤖 Generated with Claude Code