Repository navigation
fix: harden the guard, the release scanner and abcd's git calls - #883
Merged
Merged
Conversation
The guard will read a shell script that the command line points a shell at and judge it with the registry's Tier 1 rules, warning where it cannot be sure which bytes run. Assisted-by: Claude:claude-opus-5-5
The guard judged only the text of a command line, so a script file a shell ran was an allow whatever it held: `bash s.sh`, `source s.sh` and `BASH_ENV=e bash -c true`, the file form the stream refusal itself recommended. The guard now reads a shell's script operand, a source operand, BASH_ENV, and ENV for an interactive shell, and judges each with the registry's command-position matches, carrying a match out under script-runs-hazard with the script, line and entry. A script written earlier on the same line is refused (script-written-then-run); a binary handed to a shell blocks and an unreadable or oversized one warns (script-unread); a direct run is classified by its first bytes; reading shares the payload depth and a per-check budget. The hook reads from the workdir or the session directory, the check verb from its own. The stream and over-long refusals no longer recommend a file the guard does not read. Resolves: iss-2610090821484829 Assisted-by: Claude:claude-opus-5-5
bash knew --rcfile and --init-file only to step over their value, so `bash --init-file <(...) -i -c true` ran a blocker while the checked command read `bash -i -c true`. The value is now read and judged like a script, and a process substitution there is refused as a stream. The sibling sweep reads every startup file a shell-family shell takes from a place the line names: the zsh files under an assigned ZDOTDIR or HOME, a login or interactive bash's under an assigned HOME, the files a login bash or zsh reads as it exits, and the profile and rc files of dash, ksh, mksh and yash under an assigned HOME. Resolves: iss-2610090821489740 Assisted-by: Claude:claude-opus-5-5
A shell with no -c string and no script operand reads its script from standard input, and `bash < f` was an allow while `cat f | bash` blocked: the stream record was set for a pipe, a here-document and a here-string, never for a `<`. The guard now follows the descriptors a line opens on files and reads the one a shell's standard input comes from: a `<` or `0<` redirect, a descriptor duplicated onto stdin from a file opened earlier on the line, an exec that redirects the shell's own stdin, and the stdin of the shell that runs a string. Resolves: iss-2610090932257904 Assisted-by: Claude:claude-opus-5-5
`function NAME { ...; }` defines a function exactly as `NAME() { ...; }`
does, but the keyword form kept `function` and the name in front of the
body in one segment, so the walk read `function` as the program and the
body as its arguments: a warn, which the hook lets run, where the POSIX
form's body was judged. The walk now steps over the keyword and its name,
as it steps over `coproc NAME`, so the body reaches command position. The
sibling sweep found zsh's `repeat COUNT`, the same shape, and steps it too.
Resolves: iss-2610090821313095
Assisted-by: Claude:claude-opus-5-5
`trap ACTION SIGNAL…` stores ACTION and the shell runs it as a command line when the signal arrives, EXIT included, but the guard read ACTION as a plain argument. ACTION is now read the way eval's arguments are; the reset and list forms carry no command, and text the guard cannot read keeps eval's verdict. mapfile/readarray -C evaluates its callback the same way, so it is read the same way. Resolves: iss-2610090821476887 Assisted-by: Claude:claude-opus-5-5
env and sudo take every operand that carries `=` before the command as an environment assignment, but the walk stepped only identifier-named ones, so it read a non-identifier word as the program. bash imports a BASH_FUNC_<name>%% variable whose value starts `()` as a function, and a command of that name runs its body. The walk now steps every such operand, and an exported function's body is judged with the inline rules. Resolves: iss-2610090925399967 Assisted-by: Claude:claude-opus-5-5
bash expands PS4 before each traced command and PS0, PS1 and PS2 at an interactive prompt, command substitutions included, and runs PROMPT_COMMAND before each prompt, so a line that sets one and turns tracing on or starts an interactive shell runs the text in it. The guard now judges such a value wherever the line assigns it, decoding a prompt's octal escapes first, and the guard page states what it reads. Resolves: iss-2610090925390900 Assisted-by: Claude:claude-opus-5-5
… line wrote A command the registry only warns on, met inside a script the guard reads, no longer makes running the script warn, so the repository's own scripts warn on none. A file the line writes and then runs by path now blocks whatever it is, as running it through a shell already did. The ADR records both refinements, and the guard page states them. Refs: iss-2610090821484829 Assisted-by: Claude:claude-opus-5-5
The site bound now holds before any filesystem lookup, and lookups are cached across the hook's two registries, so a line naming thousands of scripts costs what sixty-four do. declare -x and typeset -x export a file-selecting variable as export does, and export -n takes it away. A script chain past the depth now blocks with the script named and its own fix, a depth-exceeded classification reads through the budget, and ln is a listed writer. Refs: iss-2610090821484829 Assisted-by: Claude:claude-opus-5-5
The script reading's word dequoting reads a backtick as the start of a command substitution, a shell grammar the allowlist entry already names, so the tokenizer's count rises from 23 to 25. Assisted-by: Claude:claude-opus-5-5
…ns in `ln -s /path/s.sh` makes ./s.sh, but the writer list took the source as the written file, so a script linked in and then run was allowed unread. ln now has its own target rule, and a comment on declare states what the code does. Refs: iss-2610090821484829 Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-opus-5-5
…nned The scanner's text branch sniffed the first 8 KiB for NUL bytes and UTF-8 validity, then counted the whole file as read by the text rules. Bytes past the window that are not text (a compressed member appended to prose) were never decoded and never named as a coverage gap. The branch now checks every byte; a file that fails is Unscanned with a reason that says the head looked like text. Siblings: the decoded-region rule (cover) already checks the whole region. The lifeboat conventions and repolint privacy sniffs only skip binary-looking files and claim no coverage, so they are unchanged. Resolves: iss-2610090821502084 Assisted-by: Claude:claude-opus-5-5
…ream Inflating a compressed PNG chunk stopped at the zlib checksum, and the walk covered only what came out. Bytes the chunk's length still claimed after the stream were read by nobody while the image was reported decoded. inflate now returns that tail, and the zTXt, iTXt, iCCP and IDAT arms cover it as a region, the same treatment a top-level zlib trailer and a zip entry's post-stream bytes already get. Siblings: decodeStream (zlib, bzip2) and zipEntryBody (deflate) already cover their tails; gzip refuses trailing bytes. The tar extension headers are fixed separately. Resolves: iss-2610090821499579 Assisted-by: Claude:claude-opus-5-5
tar.Reader.Next consumes GNU long-name and long-link bodies and PAX record sets ahead of the entry it returns, keeping only what it parses: the string before a long name's first NUL, the last of two long names, the last value of a repeated PAX key. The rest was read by nobody and the archive was reported decoded. decodeTar now walks the raw bytes Next consumed: a long name's string is a structural field, the bytes after its NUL are covered as a region (a compressed member there is decoded and scanned), a PAX body is a structural field whole, and each body's block padding must be zero. Siblings out of scope: header-block bytes the reader never parses (the tail of a V7 header, the slack of an old GNU sparse extension block) are covered by the raw byte scan only. Resolves: iss-2610090821512707 Assisted-by: Claude:claude-opus-5-5
The percent view and the JSON escape layers each decoded the raw line, and neither ever read the other's output, so a value spelled with both stacked (a JSON escape of the percent sign, a percent encoding of the backslash) was read by no view, by ScanText nor by Redact. lineViews now also builds the JSON layers of the percent view and the percent view of each JSON layer, each mapped back to the raw line through both position maps, within the existing layer caps. Siblings: the glued sweep, the literal-home backstop and DecodedViews (the harness-leak and privacy lint rules) read lineViews and inherit the composition. The pre-commit name guard already decodes a superset; its comment saying the scanner does not is corrected in both copies. Resolves: iss-2610090821491948 Assisted-by: Claude:claude-opus-5-5
… sparse header Two windows of bytes tar.Reader.Next consumes were still read by nobody while the archive was reported decoded. An extension header with no entry after it is consumed by the Next call that then reports io.EOF, and the walk broke on io.EOF before covering that call's window, so a long name placed last carried an unread member. And Next reads past an entry's own header for a sparse map (the extension blocks of an old GNU sparse entry, the map block of a PAX sparse 1.0 entry), parsing only part of it; the walk stopped at the entry's header. decodeTar now covers the window on io.EOF too, and the bytes past the entry's header must be zero beyond what the reader parsed, or the archive is not promoted. Siblings out of scope: header-block bytes the reader never parses (the tail of a V7 header, the slack of the four sparse entries inside an old GNU header) are still covered by the raw byte scan only. Refs: iss-2610090821512707 Assisted-by: Claude:claude-opus-5-5
Composing the two decoders once each way left a third alternation unread: a percent escape of the backslash of a JSON escape of the percent sign, and its mirror, each need three decodes in turn, and no view read them. lineViews now runs two alternating chains, one opening with each decoder, one pass at a time, turning to the other decoder after each pass and staying with the same one only when the other decodes nothing, up to four passes in all. Every pass is a view mapped back to the raw line; a view an earlier one already holds is dropped. The work per line stays a constant number of linear passes, and a spelling deeper than four passes stays raw, the stated residual. Siblings: the glued sweep, the literal-home backstop and DecodedViews read lineViews and inherit the alternation. Refs: iss-2610090821491948 Assisted-by: Claude:claude-opus-5-5
A skip fragment sent every path it matched to the byte-only branch, which drops the identity and network rules, and the file was never counted as a coverage gap: a fragment of "." matched every dotted path, so an address in an included markdown file shipped while an undotted LICENSE kept the zero-coverage sentinel quiet. A file a skip fragment alone matches is now Unscanned with its reason, so the launch refuses and names it; a file whose extension or name is on the reviewed skip lists takes the byte branch as before. A file left out on purpose is declared in the scanner config's new exclude_path_fragments field, each entry a fragment and a required reason. A matched file is reported excluded by choice with that reason (scan.excluded, scan.excluded_why, and a count in the gate row), is never read or counted as scanned, and does not refuse; a bundle the exclusions leave with no file scanned in full still trips the zero-coverage sentinel. A blank fragment or a missing reason is a config fault and the scanner fails closed. commands/launch.md documents both report fields and the config field. Siblings: skip_filenames match a whole name and skip_dirs is parsed but unused. A config-added skip extension still takes the byte branch. Resolves: iss-2610090821506490 Assisted-by: Claude:claude-opus-5-5
…unscanned An extension or filename a repository added through skip_extensions or skip_filenames in its scanner config joined the reviewed skip sets, so a file it matched took the byte-only branch, which drops the identity and network rules, and was never counted as a coverage gap: a repo-added ".md" let an address in a markdown file ship. Only abcd's bundled binary lists now count as reviewed. A file a repo-added entry alone matches is Unscanned with the entry named in its reason, exactly like a file a skip fragment alone matches, so the launch refuses and names it; a declared exclusion (exclude_path_fragments) still leaves it out by choice, and a bundled extension such as .png takes the byte branch as before. commands/launch.md says so, and notes that an extension-shaped fragment such as ".jar" is enough to exclude a kind of file, matching anywhere in the path. The plaintext ScannedBinary tier is now reachable only from the bundled lists, which carry no plaintext name; its test places the name itself. Refs: iss-2610090821506490 Assisted-by: Claude:claude-opus-5-5
An exclusion fragment such as "." matches nearly every path, so it names no part of the tree a reviewer can weigh; it now makes the scanner unavailable, as a blank fragment does. The decoders' pass-cap comments now say the alternating chain may read one layer deeper than the pre-passes. Refs: iss-2610090821506490 Assisted-by: Claude:claude-opus-5-5
Every isolated git command now carries log.showSignature=false, kept with the hooks and fsmonitor pins in one shared list (gitutil.ExecPins), so a log or show over a signed commit no longer starts the repository's gpg.program. The lifeboat probe, the site history load, the mention walk and the decisions-append subject read all reach git through it. Resolves: iss-2610090821531394 Assisted-by: Claude:claude-opus-5-5
The launch dirty check's diff against HEAD re-hashes the working tree when the index stat no longer matches, and did so through the repository's own filter.<name>.clean or .process program. gitutil.FilterOverrides now blanks every configured filter before the subcommand, so the check compares bytes, still lists a real edit, and fails closed on a filter the repository marks required. DirtyPayloadFiles reads through the same call. Resolves: iss-2610090821548169 Assisted-by: Claude:claude-opus-5-5
The armed agent-contract bump check parsed a unified diff that git handed to the repository's diff.external, diff.<driver>.command or diff.<driver>.textconv program, so that program ran and its stdout decided whether a prompt_version bump was seen. The diff now passes --no-ext-diff and --no-textconv, as the decisions-append diff already does, and both of the check's diffs blank the repository's content filters first, since a single-revision range compares the working tree. Resolves: iss-2610090821531570 Assisted-by: Claude:claude-opus-5-5
The pick's record commit and a sync's merge commit run through pickGit, which keeps the repository's config, so a repository-local commit.gpgsign=true started gpg.program, gpg.ssh.program or gpg.ssh.defaultKeyCommand under a commit that otherwise runs no hook. commit.gpgsign=false joins the shared pin list (gitutil.ExecPins), and pickGit now prepends that list rather than its own copy of the pins. Content filters still run on the commit. Resolves: iss-2610090821520843 Assisted-by: Claude:claude-opus-5-5
A repository that sets merge.verifySignatures=true makes git merge verify the merged tip's signature, which starts gpg.program or gpg.ssh.program for a tip whose commit object carries a gpgsig header. The sync's merge goes through pickGit, which keeps the repository's config, so it ran that program. merge.verifySignatures=false joins the shared pin list (gitutil.ExecPins), which pickGit and every isolated command prepend. Refs: iss-2610090821520843 Assisted-by: Claude:claude-opus-5-5
The consistency pass's dirty check diffs the working tree against the pinned commit, and over a corpus whose index stat no longer matches git re-hashes each document through the repository's clean filter, a program the repository names. dirtyCorpusPaths now prepends gitutil.FilterOverrides, as the launch dirty check does, so the diff compares bytes; a filter git still insists on fails the read rather than reading as clean. The emit and the ingest both reach it. Refs: iss-2610090821548169 Assisted-by: Claude:claude-opus-5-5
The release receipts check lists what is uncommitted under the reviews directory with git status, and over receipts whose index stat no longer matches git re-hashes each one through the repository's clean filter, a program the repository names. The status now runs under gitutil.FilterOverrides, so it compares bytes; a filter git still insists on fails the check rather than reading as committed. Refs: iss-2610090821548169 Assisted-by: Claude:claude-opus-5-5
The command reference now says, in present tense, that the pick's record commit and a sync's merge commit run no hook and are unsigned even where the operator's git configuration signs every commit, that the sync's merge does not verify the merged commit's signature, and that the land commit is signed as that configuration says. abcd launch and abcd launch ship now carry a long help stating that the dirty-tree comparison runs with the repository's content filters switched off, and that a filter marked required refuses the comparison over a tree whose saved file timestamps do not match, which the gate and the cut report as unreadable rather than clean. Refs: iss-2610090821520843 Refs: iss-2610090821548169 Assisted-by: Claude:claude-opus-5-5
The implement loop's lane sync merges the default branch in through pickGit, which keeps the repository's config, so a merge driver the repository configures, selected by an attribute or by merge.default, ran as the operator wherever both sides changed a path and its bytes became the merge result. gitutil.MergeDriverOverrides enumerates every merge.<name> the repository configures, through the same isolated config view and the same refusal of a name git -c cannot carry as FilterOverrides (both now share configNames), and replaces each driver with git merge-file over the three versions, with git's marker size, conflict labels and histogram diff, and pins merge.default to the built-in text driver. The result, clean or conflicted, is byte for byte git's built-in merge. A merge the operator runs is untouched. pickGit names the subcommand past any leading -c overrides in its errors. Resolves: iss-2610090821510097 Assisted-by: Claude:claude-opus-5-5
Four test files this branch added spawned git without the shared gittest helper, which TestTestGitCallsAreHermetic refuses. The two in core take their environment from gittest.Env; the two in gitutil move to the external test package, where gittest can be imported without a cycle, and call the exported functions they test. Refs: iss-2610090821531394 Refs: iss-2610090821548169 Assisted-by: Claude:claude-opus-5-5
gitutil.Status, the one reader of git status behind abcd peers, the capture ledger's uncommitted marks, the cold-reading assembler's dirty check and the interview's tree watch, ran a working-tree status with the repository's content filters live, so a file whose saved stat no longer matched was re-read through filter.<name>.clean, a program the repository names. Status now blanks every configured filter (gitutil.FilterOverrides) unless the person lists the checkout in ~/.abcd.noindex/filter-roots, read through fsutil.HomeDeclarationNames as trusted-roots is: one absolute path per line, honoured only while the file is a regular file this account owns that no one else can write, reached through no symlinked folder. The file lives in the home because a repository could otherwise switch its own filters on. The install guide documents it. gitutil now imports abcdhome, so abcdhome's two worktree-repair tests, which build their fixtures with gittest, move to the external test package to keep the import graph acyclic. Refs: iss-2610090821548169 Assisted-by: Claude:claude-opus-5-5
gitutil.Status, the launch dirty check, the consistency pass's dirty check and the release-receipts status blanked every content filter the superproject configures, but without --ignore-submodules git starts a status inside each checked-out submodule, which reads the submodule's own config and so ran a clean filter the overrides could not name. Each now passes --ignore-submodules=dirty (the flag, since a repository's submodule.<name>.ignore=none beats the diff.ignoreSubmodules config), and so does the agent-diff path list, whose one-revision range compares the working tree. A submodule moved to another commit still reads as a change; uncommitted content inside one no longer does, which the launch and launch ship help, the generated command reference and the install guide now state. gittest gains AddSubmodule for the fixtures. Resolves: iss-2610091935327982 Assisted-by: Claude:claude-opus-5-5
With diff.submodule=diff in a repository's config, a patch diff over a moved submodule pointer starts a second git diff inside the submodule. That child reads the submodule's own config and is passed none of the parent's --no-ext-diff or --no-textconv, so record-lint's patch diffs (decisions-append, agent-diff) could run a diff.external or textconv program the submodule names and parse its output. gitutil.ExecPins now forces diff.submodule=short, so every isolated command, and pickGit which prepends the same list, shows a submodule as its pointer change alone. Resolves: iss-2610091935325886 Assisted-by: Claude:claude-opus-5-5
pickGit keeps the repository's config through ScrubbedEnv, which lacks the isolated environment's maintenance pins, so a repository with a low gc.auto or gc.autoPackLimit made the pick commit or the sync merge start maintenance run --auto and gc --auto, and with them gc.recentObjectsHook, a program the repository names; a merge in a partial clone could also fetch a missing object through the promisor remote. pickGit now passes -c gc.auto=0 -c maintenance.auto=false, where they outrank the repository's config, and runs with GIT_NO_LAZY_FETCH=1. Resolves: iss-2610091935334207 Assisted-by: Claude:claude-opus-5-5
A ~/.abcd.noindex/filter-roots file that fails its ownership, mode or symlink checks was ignored without a word: Status discarded the reason, so a checkout its owner listed to keep its content filters running read with them off and nothing said why. FiltersSwitchedOn now renders the reason as one line naming the file in tilde form and the check it failed, the way the trusted-roots and local-transcript-roots readers do, and gitutil.FilterRootsIgnored gives it to a front door. abcd ahoy reports it from any folder as the report-only machine-scope gap filter_roots.ignored. The status reads themselves have no output channel and still drop it. The ahoy command page and the install guide name the gap. Resolves: iss-2610091920437492 Assisted-by: Claude:claude-opus-5-5
The command reference now says, in present tense, that the pick's record commit passes the entry it stages through the repository's clean filter and that a sync's merge passes each file it writes through the smudge filter, as Git LFS needs, in the lane's worktree, where the implementer already runs the repository's own code. Refs: iss-2610090821520843 Assisted-by: Claude:claude-opus-5-5
The board named an ignored filter-roots file by its title alone, while the docs promise the check it failed; the check and its repair now print on a filters line, as the unlinked-worktree line does. Refs: iss-2610091920437492 Assisted-by: Claude:claude-opus-5-5
A repo-local core.worktree is resolved relative to the .git directory, so `core.worktree=../..` named the checkout's parent as the toplevel. That answer contains the directory asked about, so the containment check let it through and every store addressed through CheckoutRoot (decide, intent, spec, memory, capture) was laid in the parent. Toplevel now asks git for --absolute-git-dir in the same invocation and accepts the toplevel only when its .git is that directory, or a gitfile naming it (linked worktree, submodule, separate git dir). iss-2610090821543020 Resolves: iss-2610090821543020 Assisted-by: Claude:claude-opus-5-5
In a partial clone, git answers a read of a missing object by fetching it, and the fetch runs the transport the repository's own config names (remote.<name>.uploadpack for a local URL, core.sshCommand for ssh://). gitEnv now sets GIT_NO_LAZY_FETCH=1, so such a read fails and starts no program. A present object still reads. Run, RunLimited and IsolatedEnv share gitEnv, so the tag listing is covered by the same line. iss-2610090821527948 Resolves: iss-2610090821527948 Assisted-by: Claude:claude-opus-5-5
The brief stage refused a lane worktree that was not the path derived from the run and lane, but the land stage only checked the string was non-empty, so a hand-written state file had the close lay the local tier in another directory. The check is now one helper applied wherever the lane's worktree is acted on or handed out: land (before and after the landing is recorded), implement and validate (the driver starts an agent there), and the two worktree removals (hold discard and hand-back discard), which a planted state could aim at a peer's worktree of the same repository. iss-2610090821552801 Resolves: iss-2610090821552801 Assisted-by: Claude:claude-opus-5-5
GIT_NO_LAZY_FETCH, which the isolated environment sets so a missing object in a partial clone is an error rather than a fetch through the transport the repository configures, is honoured from git 2.44; an older git ignores it, and Apple's Command Line Tools ship 2.39. One check in gitutil now runs before every isolated git command: it reads `git version` once per process and, below 2.44, refuses a command that can read objects in a repository declaring extensions.partialClone or a true remote.<name>.promisor (read through the same isolated config view), naming the floor. Commands that read only config, refs or the index (flag-only rev-parse, config, check-ignore, symbolic-ref, ls-files, worktree list) are exempt, so root discovery still answers; a repository with no promisor remote is unaffected. The tag listing now goes through gitutil.Run and so takes the same check. The resolved record states the floor, and the README's requirements name it. Refs: iss-2610090821527948 Assisted-by: Claude:claude-opus-5-5
The hand-back discard refused a branch outside the loop's prefix, but the hold discard (`implement step --discard`) deleted the lane's branch from the state alone, so a hand-written state naming `main` deleted the default branch. It now refuses such a branch before it removes anything. The land stage, which pushes the lane's branch and deletes it once landed, takes the same refusal: with main's tip as the judged head it pushed `main`. Refs: iss-2610090821552801 Assisted-by: Claude:claude-opus-5-5
… directory git follows a .git that is a symlink to the repository's git directory, but the toplevel identity check read the entry without following it, so such a checkout had no root. A symlink whose target is the discovered git directory is now held as a gitfile naming it is. Refs: iss-2610090821543020 Assisted-by: Claude:claude-opus-5-5
The partial-clone floor exempted check-ignore and ls-files as reading only the index, but git reads a skip-worktree .gitignore or .gitattributes that is missing from disk out of the object store. check-ignore is now exempt only with --no-index; ls-files only when every flag is an index-only listing flag and no pathspec carries attr magic, so the exclude flags, --with-tree, --eol, --format and -m count as reading objects; config --blob counts too. On git older than 2.44 a repository declaring a promisor remote refuses them before git starts; a repository with none runs every form as before. Resolves: iss-2610091935324732 Assisted-by: Claude:claude-opus-5-5
… 2.44 Below git 2.44, in a partial clone whose config enables core.sparseCheckout or index.sparse (read through the same isolated config view, or set by a -c on the command line), every ls-files now counts as reading objects and is refused: expanding a sparse index reads tree objects. A partial clone without those settings keeps the index-only allowance. The lifeboat probe's ignore listing and lint's ignore pruning no longer treat that refusal as "git could not answer". Probe and Plan refuse with the floor named instead of widening the default scan to ignored files, and a walk that runs anyway narrows every path; IgnoredUnder returns the refusal, so Lint, DocumentsInRoots and PrunedInRoots report it rather than silently not pruning. Other git errors keep their handling. SwapGitVersionForTest lets a test outside gitutil put git on either side of the floor. Refs: iss-2610091935324732 Assisted-by: Claude:claude-opus-5-5
git expands a sparse index by reading tree objects whenever the index file carries the sparse-directory extension, whatever the config says, so the config could not vouch for an index-only ls-files: below the floor, a partial clone now refuses every ls-files. Refs: iss-2610091935324732 Assisted-by: Claude:claude-opus-5-5
The restart derived and checked the lane's worktree path with its own copy of the check every other stage makes through loopWorktree. Both now ask derivedLaneWorktree; the restart still also holds the branch to the loop's. Assisted-by: Claude:claude-opus-5-5
The test committed with no user.name or user.email, which passes where git can guess an identity and fails on a CI runner where it cannot. Assisted-by: Claude:claude-opus-5-5
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.
A batch of hardening fixes, landed together ahead of the v0.13.4 cut.
source, a stdin redirect, a startup file the line selects) and judges it, and judges the text a line hands bash throughtrap, prompt variables and exported functions. The decision is recorded in a new ADR.Each fix carries a test watched failing before it and passing after, and each change was reviewed independently.
make preflightis clean on this head.Assisted-by: Claude:claude-opus-5-5