fix(remote): keep curl's stderr on the GET path too (#850) - #854
Open
fujibee wants to merge 4 commits into
Open
Conversation
fujibee
added a commit
that referenced
this pull request
Aug 17, 2026
tests/test_remote_curl_stderr_get.bats, 6 ok / 0 not ok, driving the production
_remote_http_get_json. Reverting the GET change turns the first test red.
A SEPARATE FILE, not a parameterised one over both helpers. These are two
functions with two shapes -- the GET side has no fifo, no copier, its own trap
and its own cleanup -- and they were fixed in two pull requests precisely so a
mutation in one cannot be covered by the other's tests. Sharing a file would
put that back: reverting the GET change would redden a case whose name says
POST.
failing curl the diagnosis reaches the caller's stderr
succeeding curl nothing does, and the stub wrote something anyway
both the http code is what it always was, 200 and 000
both no scratch file left in the run's own TMPDIR
control the leftover check can see a planted leftover
shape the request still carries the team header, so the cases
above are reporting on a GET and not on something reshaped
M0 no mutation 0 red
M1 stderr discarded again 1 -- the diagnosis reaches stderr
M2 stderr shown on success too 1 -- success stays quiet
M3 the error file never removed 1 -- no scratch left behind
M4 failure reports curl's exit code 3 -- the code contract and the two
cases that read it
THE MUTATIONS ARE CONFINED TO THE GET FUNCTION'S LINE RANGE, re-derived from
the file each round. On this branch the POST helper carries the same lines
verbatim, so a file-wide substitution would have mutated #853's fix and
reported the result under #854.
And the first run of that matrix was wrong in a way worth recording. The perl
replacements contained $curl_err and $curl_status unescaped, so perl expanded
them as its own variables -- to nothing. M2 became "test an empty string" and
M4 became "assign an empty code", neither of which is the mutation named. Both
still produced red, and the red looked plausible: M2 reddened the failure case
instead of the success case, which is the only reason I looked. A mutation that
does something other than what its label says is a false entry in this table,
not a stricter one.
NOT COVERED, and named rather than left to the matrix to imply: the scratch
file is removed on the two normal paths, and the trap set earlier in the
function does not name it. The signal path is unmeasured -- a reading of the
trap's text, not a measurement. Adding curl_err to that list is one line, but
it changes the fix rather than testing it, so it is the author's call.
Production untouched: the diff is this one test file. Windows unverified by me.
fujibee
added a commit
that referenced
this pull request
Aug 18, 2026
tests/test_remote_curl_stderr_get.bats, 6 ok / 0 not ok, driving the production
_remote_http_get_json. Reverting the GET change turns the first test red.
A SEPARATE FILE, not a parameterised one over both helpers. These are two
functions with two shapes -- the GET side has no fifo, no copier, its own trap
and its own cleanup -- and they were fixed in two pull requests precisely so a
mutation in one cannot be covered by the other's tests. Sharing a file would
put that back: reverting the GET change would redden a case whose name says
POST.
failing curl the diagnosis reaches the caller's stderr
succeeding curl nothing does, and the stub wrote something anyway
both the http code is what it always was, 200 and 000
both no scratch file left in the run's own TMPDIR
control the leftover check can see a planted leftover
shape the request still carries the team header, so the cases
above are reporting on a GET and not on something reshaped
M0 no mutation 0 red
M1 stderr discarded again 1 -- the diagnosis reaches stderr
M2 stderr shown on success too 1 -- success stays quiet
M3 the error file never removed 1 -- no scratch left behind
M4 failure reports curl's exit code 3 -- the code contract and the two
cases that read it
THE MUTATIONS ARE CONFINED TO THE GET FUNCTION'S LINE RANGE, re-derived from
the file each round. On this branch the POST helper carries the same lines
verbatim, so a file-wide substitution would have mutated #853's fix and
reported the result under #854.
And the first run of that matrix was wrong in a way worth recording. The perl
replacements contained $curl_err and $curl_status unescaped, so perl expanded
them as its own variables -- to nothing. M2 became "test an empty string" and
M4 became "assign an empty code", neither of which is the mutation named. Both
still produced red, and the red looked plausible: M2 reddened the failure case
instead of the success case, which is the only reason I looked. A mutation that
does something other than what its label says is a false entry in this table,
not a stricter one.
NOT COVERED, and named rather than left to the matrix to imply: the scratch
file is removed on the two normal paths, and the trap set earlier in the
function does not name it. The signal path is unmeasured -- a reading of the
trap's text, not a measurement. Adding curl_err to that list is one line, but
it changes the fix rather than testing it, so it is the author's call.
Production untouched: the diff is this one test file. Windows unverified by me.
fujibee
force-pushed
the
fix/850-d-get-stderr
branch
from
August 18, 2026 00:32
c7ddd16 to
fa3ccdf
Compare
The POST helper stopped discarding curl's stderr in the previous commit. The GET helper still did, and the argument is identical: it reports "000" for every kind of failure alike, so without stderr there is nothing anywhere saying why. `pull` goes through this path. A failure there was as undiagnosable as the `connect` one that cost a Windows run its afternoon — and the Windows fix did not reach it, because that work was scoped to the helper the failure happened to land in. Unlike the POST helper, this one embeds no paths in its curl config: the output file arrives via `-o`, which is curl's own argv, and MSYS does translate argv for a native binary. So the path defect never reached here. Only the diagnosability one did. With this, no curl call in the file discards its stderr.
tests/test_remote_curl_stderr_get.bats, 6 ok / 0 not ok, driving the production
_remote_http_get_json. Reverting the GET change turns the first test red.
A SEPARATE FILE, not a parameterised one over both helpers. These are two
functions with two shapes -- the GET side has no fifo, no copier, its own trap
and its own cleanup -- and they were fixed in two pull requests precisely so a
mutation in one cannot be covered by the other's tests. Sharing a file would
put that back: reverting the GET change would redden a case whose name says
POST.
failing curl the diagnosis reaches the caller's stderr
succeeding curl nothing does, and the stub wrote something anyway
both the http code is what it always was, 200 and 000
both no scratch file left in the run's own TMPDIR
control the leftover check can see a planted leftover
shape the request still carries the team header, so the cases
above are reporting on a GET and not on something reshaped
M0 no mutation 0 red
M1 stderr discarded again 1 -- the diagnosis reaches stderr
M2 stderr shown on success too 1 -- success stays quiet
M3 the error file never removed 1 -- no scratch left behind
M4 failure reports curl's exit code 3 -- the code contract and the two
cases that read it
THE MUTATIONS ARE CONFINED TO THE GET FUNCTION'S LINE RANGE, re-derived from
the file each round. On this branch the POST helper carries the same lines
verbatim, so a file-wide substitution would have mutated #853's fix and
reported the result under #854.
And the first run of that matrix was wrong in a way worth recording. The perl
replacements contained $curl_err and $curl_status unescaped, so perl expanded
them as its own variables -- to nothing. M2 became "test an empty string" and
M4 became "assign an empty code", neither of which is the mutation named. Both
still produced red, and the red looked plausible: M2 reddened the failure case
instead of the success case, which is the only reason I looked. A mutation that
does something other than what its label says is a false entry in this table,
not a stricter one.
NOT COVERED, and named rather than left to the matrix to imply: the scratch
file is removed on the two normal paths, and the trap set earlier in the
function does not name it. The signal path is unmeasured -- a reading of the
trap's text, not a measurement. Adding curl_err to that list is one line, but
it changes the fix rather than testing it, so it is the author's call.
Production untouched: the diff is this one test file. Windows unverified by me.
…use (#850) Rebased onto the corrected POST head, then given the same treatment. WHY THE ONE LINE REVIEW ASKED FOR DOES NOT WORK. An EXIT trap set inside a function runs after that function's frame is gone, so a single-quoted body expands `$cfg` in the caller's scope, where no local of that name exists. It removes the empty string and returns 0. Measured on bash 3.2.57 and 5.3.15, both EMPTY; the POST commit carries the run that left three real files behind. This helper's trap held only `$cfg`, and it has never removed it on an early exit either. Now both the config and the error file are baked into the trap with printf %q at set time, and curl_err is created beside the config so it exists before the trap that must sweep it. TESTS. tests/test_remote_curl_stderr_get.bats, 7 ok / 0 not ok. failing curl the diagnosis reaches the caller's stderr succeeding curl nothing does, and the stub wrote to stderr anyway both the http code is unchanged, 200 and 000 both no scratch file left in the run's own TMPDIR early exit errexit out of the middle sweeps both files control the leftover check fires on a planted leftover shape the request still carries the team header, so the rows above are reporting on a GET The early-exit case is cheaper here than on the POST side: this helper has no fifo and no copier, so nothing can be stranded on open() and the run cannot hang. Same property, fewer moving parts. REBASE. Two commits replayed onto d59284c, which is the POST fix plus its tests. The old base 1c833bb is still the second commit of this branch, so what moved is where it sits, not what it says. Confirmed after replay: the GET change is intact and the POST tests still pass here. tests/test_remote_curl_stderr.bats + tests/test_remote.bats 129 ok / 0 not ok on this head Windows unverified by me.
Rebased onto the corrected POST head and given the same shape: acquire one directory, arm the trap on it, make the config and the error file inside. The window review found applies here as well -- this helper made the config, then the error file, and only then armed the trap, so a failure at the second allocation or at the chmod stranded the first. There is now exactly one thing in existence before the trap. New case: make `chmod` fail, which happens after the directory exists and after the config is inside it, and assert nothing survives. Cheaper than the POST side's control, because this helper has no fifo and no copier to strand. AND THE ABSENCE ASSERTIONS WERE AIMED AT NAMES THIS LAYOUT NEVER CREATES. The leftover checks globbed agmsg-curl-cfg.* and agmsg-curl-err.*; both files now live inside agmsg-curl.XXXXXX. Those checks would have passed on any behaviour at all, including a leaked directory. They and their planted-leftover control now use the name the helper really mints. 8 ok / 0 not ok tests/test_remote_curl_stderr_get.bats 130 ok / 0 not ok the POST tests and tests/test_remote.bats on this head Windows unverified by me.
fujibee
force-pushed
the
fix/850-d-get-stderr
branch
from
August 18, 2026 00:57
fa3ccdf to
9acc615
Compare
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.
Follow-up to #853, and based on it rather than on
main: both touch the same shape in the same file, so stacking keeps the diff honest instead of producing a conflict for whoever lands second.What is wrong
#853 stopped the POST helper discarding curl's stderr.
_remote_http_get_jsonstill did, and the argument is identical — it reports000for every kind of failure alike, so without stderr there is nothing anywhere saying why.pullgoes through this path. A failure there was as undiagnosable as theconnectone that cost a Windows run its afternoon. The Windows fix did not reach it, because that work was scoped to the helper the failure happened to land in.What does NOT apply here, said explicitly
Unlike the POST helper, this one embeds no paths in its curl config. The output file arrives via
-o, which is curl's own argv, and MSYS does translate argv for a native binary. So #851's defect never reached this function — only the diagnosability one did.Worth stating because "the Windows fix missed a spot" would be the wrong reading. Two different problems shared one helper; only one of them was in both.
Scope
With this, no curl call in the file discards its stderr — verified by grep, not by eye:
What was verified
tests/test_remote.bats— 122 ok / 0 not ok, on this branch.Green was not evidence anything was diagnosed. When this was written, no test asserted on curl's stderr — that is no longer true, and the tests added since are described at the end of this body. What green establishes is that capturing it, and emitting it only on failure, leaves every existing caller's behaviour and every reported HTTP code unchanged.
This one is not gated on a platform: it changes the same way everywhere, which is also why the suite is a more meaningful check here than on #851 or #852.
Not measured
A team with an existing store has not been migrated on Windows, on this path or any other. The verified Windows run was a fresh team.
Tests, added at
c7ddd16d7d4btests/test_remote_curl_stderr_get.bats— 6 ok / 0 not ok, driving the production_remote_http_get_json. Production untouched; the diff is this one file.A separate file from #853's, not a parameterised one over both helpers. These are two functions with two shapes — the GET side has no fifo, no copier, its own trap and its own cleanup — and they were split into two pull requests precisely so a mutation in one cannot be covered by the other's tests. Sharing a file puts that back: reverting the GET change would redden a case whose name says POST.
2>/dev/nullrestoredEvery mutation is confined to the GET function's line range, re-derived from the file each round. On this branch the POST helper carries the same lines verbatim, so a file-wide substitution would have mutated #853's fix and reported the result here.
The first run of that matrix was wrong
Worth recording rather than quietly re-running. The perl replacements contained
$curl_errand$curl_statusunescaped, so perl expanded them as its own variables — to nothing. M2 became "test an empty string" and M4 "assign an empty code", neither of which is the mutation the label names.Both still produced red, and the red looked plausible. What gave it away was M2 reddening the failure case instead of the success case. A mutation that does something other than what its label says is a false row in the table, not a stricter one.
Not covered
The signal path, exactly as in #853: the scratch file is removed on both normal paths, the trap does not name it, and what I have is a reading of the trap's text rather than a measurement — the probe hung on this host. Adding
curl_errto the trap is one line and it changes the fix, so it is the author's call.Windows is unverified by me.
Rebased, tested, and given the same trap fix — at
fa3ccdfa8060New coordinates, since this PR is stacked:
Two commits were replayed onto that base. The old base
1c833bbis still the second commit of this branch — what moved is where it sits, not what it says.The GET helper's trap had the same hole, and the same cause
Review asked for one line: add
curl_errto the existing trap. That line does not work. An EXIT trap set inside a function runs after the frame is gone, so a single-quoted body expands$cfgin the caller's scope, where no local of that name exists — it removes the empty string and returns 0.This helper's trap held only
$cfg, and it has never removed it on an early exit either. Both the config and the error file are now baked in withprintf %qat set time, andcurl_erris created beside the config so it exists before the trap that must sweep it. The measured run behind this is in #853, where all three of that helper's files survived.tests/test_remote_curl_stderr_get.bats— 7 ok / 0 not okThe early-exit case is cheaper here than on the POST side: this helper has no fifo and no copier, so nothing can be stranded on
open()and the run cannot hang. Same property, fewer moving parts.2>/dev/nullrestoredEvery mutation is confined to the GET function's line range, re-derived each round: the POST helper on this branch carries the same lines verbatim, so a file-wide substitution would have mutated #853's fix and reported the result here.
The first run of that matrix was wrong. The perl replacements had
$curl_errand$curl_statusunescaped, so perl expanded them as its own variables — to nothing. M2 became "test an empty string" and M4 "assign an empty code". Both still went red, plausibly; what gave it away was M2 reddening the failure case instead of the success case. A mutation that does something other than what its label says is a false row, not a stricter one.On this head, the POST tests and the existing remote suite run together: 129 ok / 0 not ok.
Windows is unverified by me.
Update at
9acc615a4839— one directory here tooNew coordinates, since this PR is stacked:
The window review found applies to this helper as well: it made the config, then the error file, and only then armed the trap, so a failure at the second allocation or at the
chmodstranded the first. Same shape as the POST side now — acquire one directory, arm the trap on it, make everything inside.New case: make
chmodfail, after the directory exists and after the config is inside it, and assert nothing survives. Cheaper than the POST side's control, because this helper has no fifo and no copier to strand.The absence assertions were aimed at names this layout never creates
They globbed
agmsg-curl-cfg.*andagmsg-curl-err.*; both files now live insideagmsg-curl.XXXXXX. Those checks would have passed on any behaviour at all, including a leaked directory. They and their planted-leftover control now use the name the helper really mints.The orphaned-copier behaviour on the POST side is filed as #864 rather than folded into either PR.