Skip to content

fix(sync): the apply failure path silenced the shell's stderr, starting with #911's diagnostic - #974

Open
fujibee wants to merge 1 commit into
mainfrom
fix/apply-fail-silences-stderr
Open

fix(sync): the apply failure path silenced the shell's stderr, starting with #911's diagnostic#974
fujibee wants to merge 1 commit into
mainfrom
fix/apply-fail-silences-stderr

Conversation

@fujibee

@fujibee fujibee commented Aug 23, 2026

Copy link
Copy Markdown
Owner

Reported by @joelmitz on 2026-08-22, reproduced here.

scripts/drivers/storage/sqlite-sync.sh:1046   in _sqlite_sync_apply_fail

  exec 3<&- 2>/dev/null || true

exec with no command word does not run anything — it applies its redirections to the current shell, and they stay. The intent is "close fd 3, and say nothing if closing an already-closed fd complains". The effect is that the shell's stderr is pointed at /dev/null for the rest of the process.

$ f() { exec 3<&- 2>/dev/null || true; }
$ echo before >&2 ; f ; echo after >&2
before
$                       # "after" is gone

What it costs

_sqlite_sync_apply_fail is the failure path. It runs when an apply has already gone wrong, which is exactly when the messages that follow it matter. Everything written to stderr after the first failure — in that process — is discarded.

The first thing lost is the message #911 added this morning, naming which check returned 13. That diagnostic is what made the rest of today's work possible: the incident in #910 presented as exit 13 with nothing else, and #911 existed so that would not happen again. It was disabled a few hundred lines away in the same file, on the path where it is needed.

@joelmitz named that consequence in the report, which is the part that makes this worth fixing today rather than filing.

The fix

{ exec 3<&-; } 2>/dev/null || true

The redirection now belongs to the block.

Tests

Two, because either one alone passes while the other's regression ships:

  • The behaviour. Lifts _sqlite_sync_apply_fail's body out of the driver by line range and calls it, then writes to stderr and asserts the write is visible. The function is nested inside storage_sync_apply_pull and cannot be sourced; writing a copy of the shape into the test would keep passing while the driver regressed, which is the failure this test exists to catch.
  • The shape. Greps the file for a bare exec carrying a redirection. Catches the same mistake written a different way, anywhere in the file.

Both were confirmed to go red against the old line and green against the new one.

The other two exec sites in the tree are fine: codex-bridge-launcher.sh:13 carries no redirection, and codex-monitor.sh:128 is inside a subshell, which scopes it.

…derr

`_sqlite_sync_apply_fail` closed fd 3 as `exec 3<&- 2>/dev/null`. With no
command word, bash does not scope that redirection to anything — it applies it
to the shell, permanently. Every `>&2` after the first apply failure went to
/dev/null.

It sits on the failure path, so what it silences is the output a failing apply
is about to produce. The first thing lost is the message #911 added this
morning, naming which check returned 13 — the diagnostic that made today's work
possible, disabled a few hundred lines away in the same file.

`{ exec 3<&-; } 2>/dev/null` scopes it to the block.

Two tests, because either alone passes while the other's failure ships: one
lifts the driver's own function body out of the file by line range and calls it
(a copy of the shape would keep passing while the driver regressed), one greps
the file for a bare `exec` carrying a redirection. Both go red on the old form.

Reported by @joelmitz, who also identified #911 as the first casualty.
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