a dev build installs beside codeaf as devaf and follows the dev channel - #1333
Merged
Merged
Conversation
…deaf The installer takes --name WORD and CODEAF_INSTALL_NAME, validated against ^[A-Za-z0-9][A-Za-z0-9._-]*$ before anything is written, and writes $INSTALL_DIR/$NAME with a matching temporary file, a printed line naming the real file, and a closing version run of that file. INSTALL_NAME sits on one unindented line directly under CHANNEL so the website proxy can rewrite it the same way it rewrites the channel. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…mes this file A dev or staging build now asks its own channel at launch and gets the same one dim line when that channel has a newer build; /update and codeaf update with no channel word select it, while an explicit word or tag still wins. Two channel tags are ordered by publish moment first, then by the date in the tag, and a same-day tie with a moment missing goes to the release the API named, so a build ahead of the newest is never silently downgraded. The answer is cached per channel — update-check.dev.json and update-check.staging.json for one hour — so a devaf beside a codeaf cannot thrash one file, and stable keeps update-check.json and its 24 hours. CurlLine replaces the one constant everywhere a road back is offered: a file named devaf is told to reinstall from /get/devaf, a codeaf on a channel from /get/codeaf/<channel>, and any other name carries CODEAF_INSTALL_NAME. A source build's refusal still prints CurlCommand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d follows The update section no longer claims dev and staging launches are silent: it names the channel each build checks, the cache file and window it uses, the default channel of a bare /update, the ahead refusal, and the curl line that reinstalls this particular file. A new section answers "what is devaf" — the /get/devaf line, the file it writes beside codeaf, the general --name spelling, and the state both builds share. codeaf --help says this build's own channel is the default, and GUIDE.md carries the --name row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… devaf A dev build's notes now carry curl -fsSL https://agentfield.ai/get/devaf | bash and one sentence saying it installs that dev channel as devaf beside codeaf. Only the dev channel gets the block. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A review round caught the page overclaiming: it said exit 3 means the selected release is newer, when a dev or staging tag selected from a build of another channel also exits 3 — a channel tag and a version number cannot be ordered against each other — and a dev build asking --stable exits 0 for the same reason. The ahead line is spelled with the channel it names rather than dev alone. Two terminal tests now pin both sentences. The ordering table gains the cases the law left open: no moment at all, one moment twice on two days, and one moment twice on one day. A pair the law cannot rank — two channels, a channel tag beside a version, a tag that names no release — is neither newer nor ahead and draws no launch line. A terminal case proves a publish moment beats the date in the tag all the way through the door, an install failure proves it offers the road back to THIS file, and the installer refuses a bad name from the environment and a --name with no word. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… told about An rc build follows stable — its launch line names the stable release ahead of it, and a bare /update or codeaf update installs stable — but the curl line under that line said /get/codeaf/rc, which installs a channel the build never selects. The notice named one release and offered another. FollowedChannel is now the single answer to "which channel is this build on": dev and staging follow themselves, everything else follows stable. The launch check, the surface and the terminal door all take the curl line from it, and the two places that spelled the same rule out longhand to pick a default channel now ask it instead. CurlLine keeps its whole table, because an explicit road to any channel is still a thing a person can ask for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ting CurlLine spells CODEAF_INSTALL_NAME into the road it offers a binary installed under another file name, and the registry law counts every CODEAF_* word in the tree. The word is the shell installer's and codeaf never reads it, so it lands on the operator allowlist rather than becoming a row: a row would persist a preference this binary cannot act on. The law only went red now because origin/dev moved under the branch and widened the touched-package set; the pin has been unregistered since it was written. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"how do I exit codeaf" stopped reaching the keys page once two new sections landed in one release — this branch's shell-update section and the worker harness's headless exit-code section on dev — and both of them are genuinely about exit CODES. The page that answers the question was written almost entirely in quit, leave and close, so it lost a question its own heading spells out. It now opens with the sentence a person asks for and says exit where it used to say leaves, which puts it back at the top of that search. Neither branch is red alone; the pair is. Fixing the page rather than the probe is the rule this corpus is held to. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e asker's words The verifier's real-binary drive found four gaps between the page and the program. A build under any file name but codeaf or devaf prints its channel's line with CODEAF_INSTALL_NAME on the end, and the page never said so. The chat's own source-build refusal was unquoted; only the terminal's variant was. The reason a cross-channel --check exits 3 was written for a version number against a channel tag, which is not what two channel tags are. And "how do I keep my dev build up to date" reached the dev-server section first, so the update heading now carries the asker's words, as the devaf heading carries "different file name" and "--name". Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
santoshkumarradha
added a commit
that referenced
this pull request
Sep 21, 2026
…1333) CODEAF_TASK_BELT kept its name, its one reader and the sense of every caller. What moved is the answer it gives when nobody has spoken: bashBeltAsked was a match against "bash" and is now a lookup in a closed list of the words that mean the older road — node, legacy, off. Both engines are still in one binary. The list is closed on purpose. An unrecognised value leaves a person on the harness rather than moving them off it, because a typo in an environment variable must not be able to change which engine does the work and the failure it would cause is silent. An exported but blank value reads as unset, which is the default, which is bash. The two refusals that named the way in now name the way out. The manual pages that told a person the harness was not the default tell them it is, and name the three words that turn it off. The bench is the one place this could have been wrong without saying so: arm A ran with the variable unset, so after the flip both arms would have ridden the bash belt and the driver would have reported a comparison it never made. Both arms now name a word, and the branch that handled an absent one is gone. A hundred and forty-one tests in internal/session and cmd/codeaf were written against the node engine and said so by saying nothing. They did not become wrong when the default moved, they became silent about which road they meant. Each package's TestMain names that road once, rather than each test naming it, and a test that means the harness still sets "bash" for itself and wins. That pin has a cost, so the truth table is asserted directly in a new file: with the variable unset the belt is bash, the three words turn it off, case and surrounding space do not matter, and an unknown word moves nobody. A second test asserts the pin itself, because without it every other test in the package would quietly change what it tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Why
Santosh asked for internal dev usage — downloads and everything around them —
to be separated from the main install. Today a dev build can be installed, but
only over
~/.codeaf/bin/codeaf: its launch check is silent,/updatewith noword installs stable over it, and the curl line it offers reinstalls stable.
So a dev daily driver was three surprises away from being a stable binary again.
This makes a dev build a thing you can keep: it installs beside codeaf under its
own file name, it follows the dev channel, and every road it offers back leads to
the file you are actually running.
What changes
The product is still codeaf.
devafis only a file name — every sentencethe program prints still says codeaf, and
devaf versionprintscodeaf dev-… · go… linux/amd64. Nothing is renamed: not the state root, not thesocket names, not one identifier.
--name WORDandCODEAF_INSTALL_NAME, checkedagainst
^[A-Za-z0-9][A-Za-z0-9._-]*$before anything is written (anything elseis a usage error, exit 2, naming the rule). The binary, the temporary file, the
printed line and the closing
versionrun all use that name.INSTALL_NAMEsitson one unindented line directly under the
CHANNELline so the website proxy canrewrite it exactly the way it already rewrites the channel.
dev-*orstaging-*build asks itschannel at launch and gets the same one dim line when that channel has something
newer;
/updateandcodeaf updatewith no channel word select it. An explicitword or tag still wins —
/update stableon a devaf installs stable over that samefile, because the person asked. Stable and rc behave exactly as before; a source
build is still silent and still refused.
YYYYMMDDin the tag, and a same-day tie with a moment missing goes to the releasethe API named. The running build's own moment comes out of the release list the
check already fetches, so there is no extra request and no build-moment stamp. A
build ahead of the newest is never silently downgraded.
update-check.dev.jsonandupdate-check.staging.json, cached for one hour because dev publishes severaltimes a day and a devaf beside a codeaf must not thrash one file. Stable and rc keep
update-check.jsonand 24 hours, byte-for-byte as before.update.CurlLine(executable, channel)replacesthe one constant everywhere a road back is offered: a file named
devafis told toreinstall from
/get/devaf, acodeafon a channel from/get/codeaf/<channel>,and any other name carries
CODEAF_INSTALL_NAME=<name>. A source build's refusalstill prints the
CurlCommandconstant.~/.codeaf— keys, conversations, oneengine host per workspace — under the existing retire-or-attach law. Nothing here
changes it.
curl -fsSL https://agentfield.ai/get/devaf | bashandone sentence saying what it installs. Only the dev channel gets the block.
Validation contract, and how each line is proven
--name devaf --devwrites<dir>/devaf, prints the real file, ends with that file's version line, leaves a siblingcodeafbyte-identical; the variable does the same; the flag wins;../x,-x,"", a bad variable and a bare--nameall exit 2 and write nothinginternal/release·TestV1InstallerNameINSTALL_NAME="${CODEAF_INSTALL_NAME:-codeaf}"is one unindented line, exactly once, directly underCHANNEL;--helpnames both spellingsinternal/release·TestV2InstallerNameSeamAndHelp,TestInstallerKeepsTheWebsiteChannelSeamreleases?per_page=100internal/update·TestV3LaunchNoticeForChannelBuilds,TestSourceLaunchesNeverReachTheNetworkinternal/update·TestV4ChannelOrdering,TestV4UnrankablePairsAreNeitherNewerNorAheadupdate-check.dev.json; one hour there, 24 hours on stable; alternating launches in one profile make one request eachinternal/update·TestV5ChannelCacheFilesAndLifetimes/updateandcodeaf updatewith no word follow the running build; every explicit override wins; a check across two channels says only what it can orderinternal/tui3·TestV6UpdateChoiceFollowsTheRunningBuildUnlessOverridden;cmd/codeaf·TestV6TerminalUpdateDefaultsToTheRunningDevChannel,TestV6CrossChannelChecksSayOnlyWhatTheyCanOrder--checkexits 0 with the newest/running line; naming the tag installs it; a publish moment beats the tag's date through the whole doorcmd/codeaf·TestV7TerminalUpdateRefusesAnAheadDevUnlessTheTagIsNamed,TestV7APublishMomentOutranksTheDateInTheTag;internal/tui3·TestV7ChatUpdateRefusesAnAheadChannelBuildUnlessTheTagIsNamed/update's failure, the source refusal and a failed replaceinternal/update·TestV8CurlLineTable,TestV8InstallFailureCarriesTheCallersCurlLine;cmd/codeaf·TestV8TerminalFailureUsesTheRunningFilesCurlLine;internal/tui3·TestV8UpdateNotesUseTheRightCurlLineinternal/manual·TestV9DevafQuestionsReachTheTerminalManualand the probe tableagentfield.ai/get/devafand no other channel enters itinternal/release·TestV10DevReleaseNotesNameTheDevafInstallerVerified by hand
scripts/install.sh --name '../x' | '-x' | ''each printscodeaf: --name / CODEAF_INSTALL_NAME must match ^[A-Za-z0-9][A-Za-z0-9._-]*$and writes nothing;
--name devafagainst the real release service really doeswrite
<dir>/devaf.codeaf versiondoes not create the state root, so moving the installer'sclosing version run after the install-marker block (which is what "the last action
runs that file's version" asks for) changes no marker behaviour — only the order in
which the version line and the telemetry notice print.
go build ./... && go vet ./...clean,gofmt -l cmd internalsilent,
bash test/installer-telemetry.sh→35 passed, 0 failed, the four touchedpackages green, and
make pr-readygreen.(the manual overclaiming what
--checkdoes across two channels) is fixed in thelast commit, with two tests that pin the corrected sentences.
Not verified here
CurlLinestrips.exeand the installer appends its existingextension, both covered by tests, but no native Windows shell run was made.
agentfield.ai/get/devafis the other half of this change andships as a separate website2.0 pull request. That route rewrites two lines of this
installer — the channel and the name — and fails closed with a 502 until this
pull request reaches the
devbranch, because the line it must rewrite does notexist there yet. Land this first.
Added after the first review pass
draft took the curl line from the tag's own channel, which handed an rc build
…/get/codeaf/rcwhile the notice beside it named the stable release — it would havereinstalled a channel that build's own
/updatenever selects.update.FollowedChannelis now the single answer to "which channel is this build on" (dev and staging follow
themselves, everything else follows stable), and the launch check, the surface and the
terminal door all take the curl line from it. The two places that spelled the same rule
out longhand to pick a default channel ask it too. Proven by
TestTheChannelABuildFollowsIsStableUnlessItIsDevOrStaging,TestAReleaseCandidateNoticeOffersTheStableRoadandTestAFailedReleaseCandidateUpdateOffersTheStableRoad; all three were checked to failagainst the old behaviour before the fix, and a real stamped
v0.3.0-rc.1binary wasdriven by hand to confirm it.
CODEAF_INSTALL_NAMEis registered as operator plumbing.CurlLinespells theword into the road it offers a binary installed under another name, and the settings
registry law counts every
CODEAF_*word in the tree. It is the installer's pin andcodeaf never reads it, so it lands on the allowlist rather than becoming a row a person
could set and this binary could not act on.
release — this branch's shell-update section and santos/dev: crew family, Pareto crewing and the Model Pool, custom connections #1108's headless exit-code section on
dev, both genuinely about exit CODES — and between them they pushed the page that
answers the question out of the results. Neither branch is red alone; the pair is. The
quitting section was written almost entirely in quit, leave and close, so it now
opens with the sentence a person actually types and says exit where it said leaves.
Fixing the page rather than the probe is the rule this corpus is held to.
Verified against the real binary, by an independent lane
Head
1b9eb958dwas built six ways (stampeddev-20260917-a0cc22169334asdevafand ascodeaf,dev-20260921-ffffffffffff,dev-20260918-9258f7052379,v0.2.1, unstamped) anddriven under tmux against a local mirror of the real release list and one real dev asset,
never GitHub. Eleven ledger rows, all passing: the launch line for a behind dev build ends
with the devaf road and only the codeaf-named file gets
/get/codeaf/dev;/updateon adevaf downloads, checks, installs at the devaf path and restarts the same transcript
(
/statusnames the new build,/proc/<pid>/exeis the devaf file, sha matches theasset); an ahead dev build gets no line and both doors refuse the implicit downgrade with
the quoted sentences,
--versioninstalls it; an equal build says it is newest; thestable road is unchanged and writes
update-check.jsonwhile dev writesupdate-check.dev.json; a source build makes no request; the installer with--name devaf --devwritesdevafbeside an untouchedcodeaf, the variable form does the same, and thethree bad names exit 2 writing nothing; three alternating launches of a dev and a stable
build in one profile made two requests. Extras driven: a staging build follows staging, a
file named
winnerprints theCODEAF_INSTALL_NAME=winnerform,/update stableon adevaf takes the override and a failed download ends with the devaf line. The rc road was
driven at both heads: wrong at
1b9eb958d(/get/codeaf/rcbeside a notice namingv0.3.0), right at806be7745.The four manual gaps that drive surfaced — the third curl form and the chat's source-build
refusal unquoted, a wrong clause about cross-channel
--check, and "how do I keep my devbuild up to date" ranking behind the dev-server page — are closed in
a6d98c596, by headingvocabulary and quoted strings, never by the probe.
🤖 Generated with Claude Code