Skip to content

fix: remote NLP endpoint failures surface as errors instead of panicking - #1167

Closed
theredspoon wants to merge 1 commit into
vale-cli:v3from
theredspoon:fix/remote-nlp-endpoint-errors
Closed

fix: remote NLP endpoint failures surface as errors instead of panicking#1167
theredspoon wants to merge 1 commit into
vale-cli:v3from
theredspoon:fix/remote-nlp-endpoint-errors

Conversation

@theredspoon

@theredspoon theredspoon commented Sep 2, 2026

Copy link
Copy Markdown

Problem

nlp.TextToTokens (reached from core.TextToContext, reached from the tag CLI command) and nlp.Info.Compute (which runs during block construction, ahead of every rule's own Run) both panicked when a configured remote NLP endpoint's request failed: a network error, a non-2xx status, or a malformed response. That crashed the whole vale process instead of returning a normal, reportable error.

Separately, the shared HTTP transport (nlp.post) ignored response status codes entirely, so a non-2xx response with a technically-valid JSON body (e.g. 500 {"sents":[]}) was silently decoded as a successful, empty result instead of a failure.

Fix

  • post now rejects any non-2xx response with a real error instead of decoding its body.
  • TextToTokens returns an error instead of panicking on a failed /tag request. TextToContext and runTag (the tag CLI command) are updated to thread that error through.
  • Info.Compute returns an error instead of panicking on a failed /segment request. Its existing caller, lintProse, already wraps any error Compute returns as core.NewE100("NLP.Compute", err), that handling was already in place, only unreachable because Compute couldn't previously return an error on this path.

Each fix has its own regression test, both for post's status check directly and for the panic-to-error behavior of each affected function.

Testing

Two internal/e2e cases cover the user-visible behavior change end to end, each verified against the actual pre-fix binary (built at the parent commit) to confirm it currently panics there before confirming it passes cleanly here:

  • checks/remote-segment-endpoint-failure-reports-cleanly: a lint run that hits the Info.Compute panic path (a sequence rule, non-English Lang, NLPEndpoint pointed at a closed local port for a deterministic, dependency-free connection-refused failure).
  • cli/tag-reports-endpoint-failure: the vale tag CLI command hitting the same kind of failure via TextToTokens/TextToContext.

Full repo suite and -race are clean, including internal/e2e.

Related

Found during review of #1162, where these were originally bundled. Split out here as an independent, single-concern fix per the smaller-PR preference from #938.

theredspoon added a commit to theredspoon/vale that referenced this pull request Sep 2, 2026
…ing each sentence separately

NewSequence unconditionally narrowed every declared scope to
sentence-level, so a rule using max/min could never aggregate matches
across a paragraph's sentences. Threshold-opted-in rules now keep
their real declared scope; Run tags each sentence of that scope
separately instead of tagging the whole block once and inferring
sentence boundaries afterward, so a match can never span two
sentences by construction.

An undeclared scope on a threshold rule now defaults to paragraph
plus every other prose-container scope, matching what a plain
sequence rule's undeclared scope already reaches, via one shared
list in internal/core instead of two independently-maintained copies.

Built on vale-cli#1167, which fixes three pre-existing panics on remote NLP
endpoint failures this feature's own paths would otherwise have hit.
theredspoon added a commit to theredspoon/vale that referenced this pull request Sep 2, 2026
…ing each sentence separately

NewSequence unconditionally narrowed every declared scope to
sentence-level, so a rule using max/min could never aggregate matches
across a paragraph's sentences. Threshold-opted-in rules now keep
their real declared scope; Run tags each sentence of that scope
separately instead of tagging the whole block once and inferring
sentence boundaries afterward, so a match can never span two
sentences by construction.

An undeclared scope on a threshold rule now defaults to paragraph
plus every other prose-container scope, matching what a plain
sequence rule's undeclared scope already reaches, via one shared
list in internal/core instead of two independently-maintained copies.

Built on vale-cli#1167, which fixes three pre-existing panics on remote NLP
endpoint failures this feature's own paths would otherwise have hit.
theredspoon added a commit to theredspoon/vale that referenced this pull request Sep 2, 2026
…ing each sentence separately

NewSequence unconditionally narrowed every declared scope to
sentence-level, so a rule using max/min could never aggregate matches
across a paragraph's sentences. Threshold-opted-in rules now keep
their real declared scope; Run tags each sentence of that scope
separately instead of tagging the whole block once and inferring
sentence boundaries afterward, so a match can never span two
sentences by construction.

An undeclared scope on a threshold rule now defaults to paragraph
plus every other prose-container scope, matching what a plain
sequence rule's undeclared scope already reaches, via one shared
list in internal/core instead of two independently-maintained copies.

Built on vale-cli#1167, which fixes three pre-existing panics on remote NLP
endpoint failures this feature's own paths would otherwise have hit.
The remote-tagging and remote-segmentation call sites (TextToTokens,
Info.Compute) panicked on a failed /tag or /segment request instead of
returning an error, crashing the whole vale process rather than
surfacing a normal, reportable error. TextToContext and the `tag` CLI
command are updated to thread the error through rather than let it
panic.

The shared HTTP transport also ignored response status codes: a non-2xx
response with a technically-valid JSON body (e.g. `500 {"sents":[]}`)
was silently decoded as a successful, empty result instead of a failure.

Each fix has its own regression test.

Two internal/e2e scenarios cover the user-visible behavior end to end,
both against a closed local port so the failure (connection refused) is
deterministic and needs no network or mock server: a lint run whose
Info.Compute hits a failed /segment request during block construction,
and the `tag` CLI command's /tag request. Verified against the pre-fix
commit that both currently fail this way (a panic with a goroutine
stack trace) before this fix, and pass cleanly after it.
@theredspoon
theredspoon force-pushed the fix/remote-nlp-endpoint-errors branch from 63e807a to b41c3cc Compare September 2, 2026 03:36
theredspoon added a commit to theredspoon/vale that referenced this pull request Sep 2, 2026
…ing each sentence separately

NewSequence unconditionally narrowed every declared scope to
sentence-level, so a rule using max/min could never aggregate matches
across a paragraph's sentences. Threshold-opted-in rules now keep
their real declared scope; Run tags each sentence of that scope
separately instead of tagging the whole block once and inferring
sentence boundaries afterward, so a match can never span two
sentences by construction.

An undeclared scope on a threshold rule now defaults to paragraph
plus every other prose-container scope, matching what a plain
sequence rule's undeclared scope already reaches, via one shared
list in internal/core instead of two independently-maintained copies.

Built on vale-cli#1167, which fixes three pre-existing panics on remote NLP
endpoint failures this feature's own paths would otherwise have hit.
theredspoon added a commit to theredspoon/vale that referenced this pull request Sep 2, 2026
NewSequence unconditionally narrowed every declared scope to
sentence-level, so a rule using max/min could never aggregate matches
across a paragraph's sentences. Threshold-opted-in rules now keep
their real declared scope; Run tags each sentence of that scope
separately instead of tagging the whole block once and inferring
sentence boundaries afterward, so a match can never span two
sentences by construction.

An undeclared scope on a threshold rule now defaults to paragraph
plus every other prose-container scope, matching what a plain
sequence rule's undeclared scope already reaches, via one shared
list in internal/core instead of two independently-maintained copies.

Built on vale-cli#1167 (fixes three pre-existing panics on remote NLP
endpoint failures this feature's own paths would otherwise have hit)
and vale-cli#1169 (fixes a sentenceScope bug that review of this feature
found as a real, dispatched double-report).
@jdkato

jdkato commented Sep 2, 2026

Copy link
Copy Markdown
Member

NLPEndpoint is an undocumented experiment, referenced only in #356. There's a lot I don't like about its current implementation and I don't intend to support it in its current state.

@jdkato jdkato closed this Sep 2, 2026
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.

2 participants