Skip to content

fix(docs-hygiene,code-tidying): make the filtered-probe guard pipefail-proof - #3469

Merged
claude[bot] merged 1 commit into
mainfrom
claude/docs-hygiene-ssot-extraction-jft8sf
Aug 28, 2026
Merged

fix(docs-hygiene,code-tidying): make the filtered-probe guard pipefail-proof#3469
claude[bot] merged 1 commit into
mainfrom
claude/docs-hygiene-ssot-extraction-jft8sf

Conversation

@claude

@claude claude Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

No linked issue

Summary

#3468 fixed five filtered-probe injections so a failed probe could be told apart from a filter that matched nothing. The form it shipped is wrong under set -o pipefail, and wrong in the more damaging direction: it asserts a failure that did not happen.

probe >/dev/null 2>&1 && probe | filter | head -10 || echo "(git status unavailable)"

The && list takes the pipeline's exit status. Without pipefail that is head's, which is 0, and the guard works. With pipefail it is the last non-zero status in the pipeline, which two ordinary situations produce:

  • grep matching nothing — a repository with dirty files, none of them .md.
  • git taking SIGPIPE when head closes the pipe at the cap — any repository with more than 10 matching dirty files.

Either one fires the failure token on a healthy probe. That is worse than the defect #3468 removed: the shape it replaced only ever said none, while this one positively asserts git status was unavailable when it ran fine.

Fix

The filter pipeline moves inside a brace group closed by :, a command that cannot fail, so the || is reachable only by the guard short-circuiting:

git status --porcelain >/dev/null 2>&1 && { git status --porcelain 2>/dev/null | … | head -10; :; } || echo "(git status unavailable)"

Five injections: docs-hygiene:audit-noise, docs-hygiene:audit-progressive-disclosure, docs-hygiene:compress, code-tidying:dissolve-comments, code-tidying:audit-comment-residue. No $ expansion is introduced — the composed precompute block must stay verifiable to the worktree-isolation guard.

audit-comment-residue's parity test anchors its porcelain capture past && {, so it evaluates exactly the data run rather than an unterminated brace group. Its capture was checked to be non-empty, so the assertion still bites rather than passing vacuously.

Verification

Both failure modes reproduced against the shipped 0.21.23 / 0.14.12 lines, then shown fixed:

# grep matches nothing, healthy repo, set -o pipefail
0.21.23 form  ->  (git status unavailable)      # false failure report
this PR       ->  <nothing>                     # correct: filter matched nothing

# 3,000 dirty .md files, healthy repo, set -o pipefail  (SIGPIPE at the cap)
0.21.23 form  ->  (git status unavailable)      # false failure report
this PR       ->  10 filenames                  # correct

# not a repository, set -o pipefail
this PR       ->  (git status unavailable)      # correct: the probe really did fail

Independently, set -o pipefail; seq 1 2000000 | head -10 >/dev/null exits 141, which is the SIGPIPE mechanism above in isolation.

scripts/check-changed-skills.sh origin/main        5 skills checked, 0 failed
scripts/check-changelog-parity.sh --check          clean
scripts/check-changelog-parity.sh --check-order    86 changelogs, no duplicates
scripts/check-changelog-parity.sh --check-bump     every bumped plugin has its entry
node scripts/validate-plugin-contracts.mjs         52 setup skills, 3022 files
audit-noise detect.test.sh                         199/199
audit-comment-residue detect.test.sh               53/53
markdownlint-cli2 <changed>                        0 issues
typos <changed>                                    clean

Provenance, stated because it bears on how much to trust this

An agent proposed this fix and I twice discarded it, judging it a no-op: I had tested the shipped form only without pipefail, where head swallows grep's exit status and the guard does work. I then stopped that agent for looping when it re-applied its own correct work. Its commit message named pipefail explicitly, which is what made me re-test. Both of its claims then reproduced, including the SIGPIPE one I had failed to trigger at 15 files and which appears at 3,000.

The reviewer question this leaves open, and which I could not settle: whether Claude Code executes ! injections under pipefail. Neither plugins/playbooks/skills/skill-authoring/reference/precompute-context.md nor the permissions docs say. The brace-group form is correct under both settings and the shipped form is correct under only one, so this is the fail-safe direction regardless — but if someone knows the harness's actual shell options, that belongs in the precompute convention, which currently says nothing about it.

Related

Refs #3468, refs #3460. Corrects the form shipped in docs-hygiene 0.21.23 and code-tidying 0.14.12.


Generated by Claude Code

…l-proof

An adversarial verifier refuted the form 0.21.23 and 0.14.12 shipped, under
`set -o pipefail`. `A && pipeline || echo TOKEN` takes the pipeline's exit status,
which pipefail makes non-zero when `grep` matches nothing or `git` takes SIGPIPE at
the cap, so the failure token fired on a healthy probe: the exact inversion of the
defect the fix exists to remove, and a positively false claim where the shape it
replaced only said `none`.

The filter pipeline now sits in a brace group closed by `:`, a command that cannot
fail, so the `||` is reachable only by the guard short-circuiting:

  git status --porcelain >/dev/null 2>&1 && { git status --porcelain 2>/dev/null | … | head -10; :; } || echo "(git status unavailable)"

Verified on the committed lines in five states, with and without `set -euo pipefail`:
outside a repository, with a `.git` that is not a repository, with no `git` on PATH,
in a repository whose dirty files do not match, and at the cap with 15 matches.

`audit-comment-residue`'s parity test anchors its porcelain capture past `&& {`, so it
evals exactly the data run instead of an unterminated brace group, and stops evaluating
an arbitrary shell list. Both suites green (53 and 199 checks).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo
@claude
claude Bot merged commit 41b55be into main Aug 28, 2026
59 checks passed
@claude
claude Bot deleted the claude/docs-hygiene-ssot-extraction-jft8sf branch August 28, 2026 09:25
kyle-sexton pushed a commit that referenced this pull request Aug 28, 2026
…date

The 0.2.0 entry presents a Phase 6 measurement whose six figures are
actually Phase 3 ones: 1,347 files / 525 candidates / 482 parsed / 43
declined / 0 expired / oldest 2026-04-08 all reproduce exactly at 33dccc5,
the commit that introduces list-corpus.sh, on a clean tree with the scripts
as they existed there. They were carried forward without re-measuring.

Records the current baseline at 619199e with --as-of 2026-08-28: 1,352
tracked markdown after carve-outs (1,395 considered, 43 declined at path
level), 535 candidates, 491 parsed, 44 declined at stamp level (20
month-name, 24 bare years), 0 expired at the 180-day default, oldest parsed
stamp 2026-04-08, 9 findings at a 60-day window.

Two of those figures expire, so the as-of date is pinned beside the commit:
0 expired holds only until 2026-10-05 on the current oldest stamp, and the
60-day finding count moves daily. A baseline recorded without one repeats
the staleness this entry corrects.

Also corrects the delta's attribution. It is not main moving across #3467
to #3469 — those contribute +1 in total, one file in #3468. #3467 adds 20
markdown files and contributes zero, all of them inside the excluded
fixtures tree, which is why considered rises by 20 and the fixture decline
goes 3 to 23 while the corpus is untouched.

An earlier draft of this entry claimed the figures came from no commit at
all. That replay covered only history reachable from main, which the
squash-merge of #3467 had made exclude the originating build branch; the
reflog held it throughout. The entry now records that a history replay
bounded at a squash boundary cannot answer whether a number came from a
commit. Refs #3465.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018tvKPLnWr7suxxAupU4p8A
kyle-sexton pushed a commit that referenced this pull request Aug 28, 2026
…a spaced skill dir

#3469 fixed five filtered probes whose `||` fallback fires spuriously under
`set -o pipefail`, and stopped there. The same shape is still live at eleven
more sites across six plugins, four of them introduced by #3468 -- the commit
immediately before the one that diagnosed the bug class.

The mechanism is unchanged. `guard >/dev/null 2>&1 && probe | cap || echo TOKEN`
takes the pipeline's exit status. Without pipefail that is the cap's, always 0.
With pipefail it is the last non-zero status, which two ordinary situations
produce: a filter matching nothing, and the writer taking SIGPIPE when the cap
closes the pipe. Either fires the failure token on a healthy probe.

ai-slop:audit is the worst and was live, not latent. detect.sh --show-config
emits 10 lines against a head -8 cap, writing one at a time, so head always
closes the pipe mid-run: PIPESTATUS reads 141 0. The probe rendered the full
correct config and then appended "detector unavailable" beneath it. It
reproduced from any directory with no configuration at all.

knowledge is the one with a consequence beyond a wrong string. video-digest
states "STOP if the pre-computed context above shows MISSING for yt-dlp,
ffmpeg, or ImageMagick. Cloud agents without the media toolchain fail closed."
#3468 fixed that gate's fail-open defect and shipped this inversion in its
place, so the gate would refuse to run on a machine that HAS the toolchain.
Whether the token fires is a race between the tool writing its banner and the
cap closing the pipe, so it scales with output size. Measured here over 30 runs
per size against a synthetic tool on PATH, installed and working: 41 lines
fired 29/30, 5 lines fired 19/30, 1 line fired 0/30. So ffmpeg fails on nearly
every run, magick (5 lines) is a genuine race, and yt-dlp (1 line) is latent by
shape only. An intermittent false MISSING is harder to diagnose than a
consistent one; all three are fixed regardless.

Also fixed here, same shape: course-digest's two probes, firecrawl:firecrawl
(pre-existing, and the idiom the fleet copied), provenance:audit's two detector
probes, docs-hygiene:audit-derivability -- whose capture-first form protects
only the git call, leaving its awk-and-head data run exposed past 20 dirty
files -- and code-tidying:tidy, where a bare third "unknown" line was appended
to well-formed output.

Two of the eleven are latent by shape, not observed failures, and the
provenance changelog says so plainly rather than claiming a live fix:
list-corpus.sh --show-config emits 7 lines against a head -10 cap, and
check-stamps.sh is piped into tail -3, which drains its input and cannot raise
SIGPIPE. Both were verified to behave identically before and after in three
states under both settings.

Separately, ${CLAUDE_SKILL_DIR} is now quoted in ai-slop:audit and
provenance:audit. Installed under a path containing a space, the unquoted
expansion made a working detector report itself unavailable. firecrawl:update
already quoted it; these now match. Pre-existing, but these commits are what
made the token reachable at all -- before them the same failure rendered empty.

Quoting changes the literal command string, and Bash permission rules are globs
over that literal string, so the unquoted script grants no longer match the
quoted invocations. Three companion rules are added, one per quoted script:
Bash("${CLAUDE_SKILL_DIR}/scripts/detect.sh":*) in ai-slop, and the list-corpus
and check-stamps equivalents in provenance. The unquoted rules are kept because
both skill bodies still instruct unquoted invocation elsewhere. Each pair names
one script under the same ${CLAUDE_SKILL_DIR} anchor with the same :* argument
scope, so nothing is authorized that the plugin could not already run. No other
grant changed: every other rewritten command keeps its original leading token,
which is why code-tidying:tidy takes the ||-inside-the-brace-group form rather
than the brace-group-first one.

The remedy elsewhere is the brace group #3469 established: the data pipeline
sits in { ...; :; }, closed by a command that cannot fail, so the || is
reachable only when the guard short-circuits. No new idiom, and no $ expansion
introduced beyond the quoted ${CLAUDE_SKILL_DIR}; audit-derivability keeps the
pre-existing $s capture, which is the part that legitimately drives its ||.

Every site proven by execution in three states -- tool or script absent, output
under the cap, output over the cap -- with and without pipefail. The five sites
#3469 fixed were re-verified at 3,000 dirty files under both settings and are
correct as shipped, as is claude-ops:observability, whose sed drains its input.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo
claude Bot added a commit that referenced this pull request Aug 28, 2026
…a spaced skill dir (#3474)

#3469 fixed five filtered probes whose || fallback fires spuriously under
set -o pipefail, and stopped there. Two independent verifiers found the same
shape still live at eleven more sites across six plugins, four of them
introduced by #3468 -- the commit immediately before the one that diagnosed the
bug class.

The mechanism is unchanged. guard >/dev/null 2>&1 && probe | cap || echo TOKEN
takes the pipeline's exit status. Without pipefail that is the cap's, always 0.
With pipefail it is the last non-zero status, which two ordinary situations
produce: a filter matching nothing, and the writer taking SIGPIPE when the cap
closes the pipe. Either fires the failure token on a healthy probe.

ai-slop:audit was live and unconditional. detect.sh --show-config emits 10 lines
against a head -8 cap, writing them one at a time, so head closes the pipe
mid-run every time; PIPESTATUS reads 141 0. The probe rendered the full correct
config and then appended 'detector unavailable' beneath it, reproducible from
any directory with no configuration at all.

knowledge had a consequence beyond a wrong string. video-digest states 'STOP if
the pre-computed context above shows MISSING for yt-dlp, ffmpeg, or ImageMagick.
Cloud agents without the media toolchain fail closed.' #3468 fixed that gate's
fail-open defect and shipped this inversion in its place, so the gate would
refuse to run on a machine that HAS the toolchain. Measured over 30 runs each
with the tool installed and working: ffmpeg 29/30 (41 lines), magick 19/30 (5
lines, a race), yt-dlp 0/30 (one line, never reaches the cap). After the fix,
0/30 at every size.

Also fixed: course-digest's two probes, firecrawl:firecrawl (pre-existing, and
the idiom the fleet copied), provenance:audit's two detector probes (latent by
shape only, and its changelog says so rather than claiming a live fix),
docs-hygiene:audit-derivability whose capture-first form protects only the git
call, and code-tidying:tidy where a bare third 'unknown' was appended to
well-formed output.

One grant moved, and an earlier draft of this message denied it. Quoting
CLAUDE_SKILL_DIR changes the literal command string, and Bash permission rules
are globs over that string, so the unquoted rule stops matching the quoted
command. Three companion quoted rules were added with the unquoted ones
retained; same script, same anchor, same :* scope, nothing widened. The quoting
itself fixes a real defect: installed under a path containing a space, the
unquoted expansion made a working detector report itself unavailable.

Roster completeness was re-derived by a method unrelated to the first grep, a
parser tracking brace depth and quote state, which found zero of these eleven
and nine survivors, all correctly-drawn declines or structurally immune.

Open and undocumented: whether Claude Code runs ! injections under pipefail.
The brace-group form is correct under both settings, so the exposure is
avoidable regardless.

Refs #3469, refs #3468, refs #3460.
claude Bot added a commit that referenced this pull request Aug 28, 2026
…five wrong keeps (#3475)

#3468 closed 16 cross-plugin path citations under ADR 0018 and kept 23 on a
test it never wrote down. Its own verifier reported '3 keeps refuted; 4 cannot
verify' and that reasoning was unreadable, because the verifier ran as a remote
session the agent could not read back. An independent verifier settled it: five
are wrong, not three, and it found why this recurred.

The dividing test #3468 applied -- fix a citation that is an ADDRESS for an
obligation, keep one that is EVIDENCE about this tree at a moment in time --
appears nowhere in ADR 0018. Clause 2 reads unconditionally. But ADR 0018 is
itself a docs/** file and path-cites plugin skill privates five times, quoting
their contents inline. Either it violates itself on its own first page, or
clause 2 is scoped to citations that function as an address. Only the second
reading is coherent, and an unwritten rule is what let one commit rule two ways
in two files. This writes it down, as an Amendment following ADR 0011's
convention: the evidence/address test, scripts/lib being outside the
entry-surface carve-out, and how plugin-level non-skill trees generalize.

The five wrong keeps. shell-test-helpers cited a private scripts/lib/ path --
and that is a self-contradiction inside c66f26c itself, which added
detector-findings 2.8.1 fixing the byte-identical shape and stating the ground,
then kept it one file over. Three line and step pins are dropped as the same
class 2.7.1 removed a :414 pin for; each citation already quotes its content
inline, so the drop is lossless. records.json:3's keep-ground was false: the
sweep kept it because generation overwrites hand-edits, but that store's own
note says 'Hand-editable and human-gated'.

Recorded as open remainder rather than swept: twelve citations c66f26c created
itself, every fix having written a changelog entry quoting the path it removed,
so the population is 16 fixed and 35 kept rather than 16 and 23; three trees
excluded by fiat with no carve-out authorizing it; 51 citations into
plugin-level non-skill trees; and a stale dometrain security record whose own
version-bump re-trigger has fired unnoticed.

A fifth review round found two more false claims, both stale statuses. '34 to
32' was wrong: re-deriving all 34 rows against the roster's own text test finds
22 already closed, twelve of them by #3380, the same commit that last wrote the
roster and left its own 'all 34 re-verified' line standing over twelve citations
it had just deleted. This change set republished that status in a file whose
line 18 reads 'the check is the text, never the status and never the line
number.' And the spec still shipped the probe shape #3469 removed, presented as
verified in three states, two of which print the failure token under pipefail.

Refs #3468, refs #3469, refs #3460. Sibling #3474.
claude Bot added a commit that referenced this pull request Aug 28, 2026
…test, and record the pipefail question (#3476)

The last five findings from an independent security review of #3460/#3468/
#3469. #3474 and #3475 closed the probe-shape and citation halves; this closes
the filter half and writes the open question into the convention.

Four probes silently under-reported any filename git status --porcelain
C-quotes. A plain space is enough to defeat grep '\.md$' and awk '{print $NF}'.
Executed against a repo holding seven files per extension (plain, space, single
quote, double quote, semicolon, pipe, embedded newline), each of the four went
from catching 4 of 7 to 7 of 7. Two correct forms already shipped in the same
two plugins, so this was drift rather than a missing idea: the .md sites took
audit-noise's grep form, which keeps their leading token and so keeps their
grant valid, and dissolve-comments took audit-comment-residue's -z NUL parse.
The residual is stated in the changelogs rather than hidden.

audit-comment-residue's parity test could not see an over-reporting preview.
Its loop only checked one direction, so mutating the SKILL.md awk to drop its
rename skip made the preview emit a phantom path while detect.sh audited five
real ones, and the suite still passed 53/53. A reverse loop now asserts every
previewed path is one detect.sh audited: the same mutation fails 1/54 naming the
phantom, and reverted it passes 54/54. The forward loop is byte-identical.

claude-ops:observability rendered 'unknown' both outside a repo and inside one
whose toplevel directory is literally named unknown. The failure case now
renders a distinct token.

Six labels asserted 'empty = none' in two states the probe does not establish:
the brace group's : makes the outer || unreachable, which also masks a
filter-stage failure and a guard/data double invocation. The labels now say
'empty = none matched or the probe returned nothing'. No plumbing restructured;
the ~25 unfiltered probes were left alone because their claim is already true.

The durable fix is in precompute-context.md: whether Claude Code runs !
injections under pipefail is undocumented, the brace group is correct under both
settings while guard && pipeline || token is correct under only one, and the
brace group's own price is that an internal failure renders empty, which is why
the label must carry it. The recheck trigger now covers shell options.

Two derivations unrelated to the review's table both return the same six
filtered probes: the true set is six, four defective, not five and three.
#3474 fixed audit-derivability's SIGPIPE shape hours earlier and left its filter
untouched, so both defects landed on one line; this branch was rebuilt onto
current main and carries both, since committing either alone would have
reverted the other.

70 pipefail cells all exit 0; affected-tests real exit 0 with 17/17 suites;
changelog parity including --check-preserved across 306 headings; 3061 plugin
files validated; markdownlint clean over 173 files. No grant widened or broken.

Refs #3474, refs #3475, refs #3469, refs #3468, refs #3460.
claude Bot added a commit that referenced this pull request Aug 28, 2026
…, and flag a fired security re-trigger (#3477)

Every one of #3468's ADR-0018 citation fixes wrote a CHANGELOG entry quoting
the path it removed, creating twelve new instances of the shape it was closing.
The population at main is 16 fixed and 35 kept, not 16 and 23. #3475 wrote the
governing test into ADR 0018; this applies it to those twelve so a later
re-derivation does not re-open them.

All twelve are keep-correct, established row by row and anchored on text rather
than line numbers. A dated changelog entry quoting a citation it removed is the
evidence case exactly. No CHANGELOG was edited, so no convention bump is owed.

Three sub-rulings the class needed, now written into the spec rather than left
implicit. config-cascade's three rows are the only clause-3 question in the set:
they are plugin-relative and resolve against nothing, and the entry says so in
the same sentence. Clause 3 governs addresses a doc offers, not strings it
quotes in order to report them broken, and forcing resolution would delete the
finding. The other nine resolve on disk today, verified one path at a time
across eleven distinct targets. No row carries a line or step pin, the part the
amendment says rots first.

One excluded on purpose and recorded so it is not re-opened:
plugins/review/reference/topic-docs.md, a plugin-level non-skill tree the
amendment routes to its own pass, which the entry declares a keep rather than
quoting as removed.

Separately, MIGRATION-PLAYBOOK.md records a dometrain security review performed
at 0.1.0 and states that a version bump adding a new trust surface re-triggers
it. The manifest now reads 0.2.7, eleven releases later. The note added states
the reviewed version, the shipping version, that the ACCEPT below it describes
0.1.0 only, and that the re-review is owed and deliberately not performed here.
It stops short of asserting a trust surface was added: that is the condition the
clause turns on and cannot be settled by reading the page. Two checks were run
to avoid claiming otherwise and neither settles it, which the note says.

The spec's dometrain entry pinned MIGRATION-PLAYBOOK.md:943, a line this
change's own edit would have invalidated. Re-anchored on the record's heading
text. That decay rule has been violated three times across this sweep; this is
the first time it was caught before landing.

Recorded because two earlier claims about it were wrong in opposite directions:
this worker's definition declares the Agent tool and the harness withheld it at
spawn, so nesting is gated by depth realized as pre-launch tool omission. There
is no refusal string because there is no call to refuse.

Refs #3476, refs #3475, refs #3474, refs #3469, refs #3468, refs #3460.
claude Bot added a commit that referenced this pull request Aug 28, 2026
…reopened it three times (#3478)

The L4 roster's Group 2, eight intra-plugin path-form citations, was the last
block anyone believed open. All eight were already closed, by 6c7a103 (#3380)
on 2026-08-26 -- the same commit that wrote the roster. Its own message names
what it did: eight citations written with an implied base of the plugin root
while the real base was reference/, none of which resolved for any reader.

Verified one at a time against the roster's own text test. All eight now read
${CLAUDE_PLUGIN_ROOT}/skills/<skill>/<path> and all seven distinct targets
exist on disk. Zero plugins/** files edited, so no bump and no plugin changelog
entry: nothing needed changing.

The reason four separate passes each re-derived this roster from scratch is one
sentence. #3380 fixed 32 of 34 rows and left its summary asserting that all 34
path:line citations were re-verified on 2026-08-26 and every one resolves --
standing over citations it had just deleted. A fifth-round audit caught that for
Group 1, where 22 of 32 were already closed and twelve of those by #3380 itself.
This closes the same hole for Group 2. The generalizable rule is now in the
sweep spec: a record that fixes findings and updates its own summary in the same
commit must update the summary, or the summary outranks the fix for every later
reader.

The roster's inventory rows are left verbatim, per its own decay rule that the
inventory is the part that cannot be re-derived, and per the precedent that
#3474 and #3475 both closed rows without touching the file. Only an additive
closure stamp is appended.

On whether ADR 0018 reaches this class, the honest answer is that its
encapsulation half does not. Clause 1 legalises intra-plugin citations and names
this exact citing surface. Clause 2 cannot apply, because both files ship inside
one plugin so the runtime absence motivating it cannot occur, and the 2026-08-28
amendment's fix-an-address / keep-evidence test divides clause 2 applications
only. Only clause 3 reaches Group 2, and clause 3 is a resolvability rule rather
than an encapsulation one: had these been open, the remedy would have been path
form and nothing else. The clause that earned its keep is the ADR's own
observation that proximity did not prevent them.

A second derivation making no reference to the roster resolved every citation
token in every plugin-level reference/, context/ and agents/ tree plus every
plugin README against the base its own form implies: 52 tokens, 0 clause-3
failures. Group 3's two anchors were also closed by #3380. 34 closed, 0 open.

No fresh-context verifier reviewed this diff. Nested spawning is unavailable at
this depth: the Agent tool is withheld pre-launch, so there is no call to refuse
and no refusal string. A second mechanical derivation was substituted and every
line reference re-checked against the live tree, which is weaker than a fresh
context and is recorded as such.

Refs #3477, refs #3476, refs #3475, refs #3474, refs #3469, refs #3468.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant