fix: reject an unrecognized language instead of falling back to English - #761
Merged
Colin Francis (colifran) merged 4 commits intoAug 28, 2026
Conversation
An unrecognized `language` resolved to English and was recorded in run state, which resume refuses to change, so the only way out was deleting OpenWiki's own state files. Both entry points now reject it before anything is written, and ResolvedLanguage is a discriminated union so callers can no longer collapse "unrecognized" into "absent". The openwiki_begin tool description and schema now state that the value must be a BCP-47 code, so callers pass `ko` rather than `Korean`, and the `--language` help text names the same form. `--language` with an unrecognized value exits 1 instead of generating an English wiki.
🦋 Changeset detectedLatest commit: c02e439 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
…e' into fix/reject-unrecognized-language
Colin Francis (colifran)
approved these changes
Aug 28, 2026
Colin Francis (colifran)
left a comment
Collaborator
There was a problem hiding this comment.
Thanks, easyhak!
Merged
Brad Huffman (ppsplus-bradh)
added a commit
to ppsplus-bradh/openwiki
that referenced
this pull request
Aug 28, 2026
Pick up 4 upstream fixes (unrecognized-language rejection langchain-ai#761, GitHub Copilot non-GPT-5 streaming langchain-ai#744, Bedrock maxTokens langchain-ai#743, docs langchain-ai#759). One conflict, in beginRepositoryRun (src/generation/repository-run.ts): upstream langchain-ai#761 now resolves and rejects an unrecognized language before touching the repository, right where the feature computes its skipRepoSetup guard and noopScope. Kept both — the language rejection runs first, then the recursive repo-setup guard and the subproject no-op scope. Full suite: 3680 passed / 3 skipped. Co-Authored-By: Claude <noreply@anthropic.com>
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.
What & why
Passing a language name (e.g.
Korean) instead of a BCP-47 code (ko) toopenwiki_begin/--languageused to print a warning and silently fall back to English.The first call then persisted the run at
en, and because resume refuses to change a started run's language (andforceis not honored on that path), the only escape was manually deletingopenwiki/.run.jsonand.last-update.json.This is not hypothetical. It happened in two real project sessions.
/openwiki 초기화를 한국어로 진행해줘language: "Korean"/openwiki … in koreanlanguage: "korean"Both then hit the same dead end
The failure sequence unfolded like this
The fix
An unrecognized language is rejected before any run state or repository mutation exists, at every entry point. No wrong-language run can be persisted — on any host, or via raw
--language— and a rejected request costs nothing but a retry.The tool description carries its share of the work too, but the two do different jobs:
I measured both, because it was not obvious up front that the description alone would be enough (it isn't).
Experiment in Claude Code
40 isolated runs — fresh session and fresh temp git repo each — on Opus 5, Sonnet 5, and Haiku 4.5, using the real skill and
--host claude, recording the actual JSON arguments the server received rather than the model's self-report.Rigor notes
diff -rqover the wholedistconfirmed the only difference between variants was the intended lines.1. Does the description block the bad value?
3 models × 2 builds × 5 runs = 30. The "+ description" build adds only the description and schema hint — not the rejection. Metric: the
languagevalue thebegincall passed. Zero timeouts."Korean"×5"ko"×5"ko"×4,"Korean"×1"ko"×5"ko"×4,"한국어"×1"ko"×5Baseline 7/15 invalid (47%) → 0/15 (0%). Fisher exact two-sided
p = 0.0063; Opus alonep = 0.0079.The strongest model(Opus) failed most: with no hint in the schema, "put the human-readable language name" is the natural reading.
2. Is a warning enough on its own?
Before settling on rejection I tried a response-side
languageWarning, with the description reverted, and ran realbegincalls on Opus 5 - checking the final.run.jsonlanguage rather than just the argument."Korean"×5"ko"×5The warning brings the diagnosis forward but does not close the trap. Opus corrected to
koon the very next call every time, yet 3/5 still ended in English because the first call had already committed the run toen.Changes
src/platform/language.ts— replace{ language?, warning? }with a three-caseResolvedLanguageunion (absent | resolved | unrecognized), so a typo can no longer be collapsed into "no language requested". The type change alone surfaced every call site that was silently dropping the warning. AddsrequireResolvedLanguage()as a post-boundary guard that throws if an unrecognized value ever slips past an entry point.src/generation/repository-run.ts— reject an unrecognized language withinvalid_inputat the top ofbeginRepositoryRun, ahead of bothensureCodeModeRepoSetupand the resume branch, so neither a fresh nor a resumed run can be started with one.src/cli/commands.ts—parseRunCommandexits1on an unrecognized locale instead of surfacing a warning;languageWarningis removed fromCliCommand. The-l, --languagehelp text now names the expected BCP-47 form.src/cli/cli.tsx— drop the now-unused stderr warning write.src/integrations/core/protocol.ts,session-manager.ts— document thatlanguageis a BCP-47 code and that an unrecognized value fails withinvalid_inputand starts no run.Behavior that is deliberately unchanged: a recognized language that differs from an interrupted run's still returns
conflict. This PR only removes the case where the run was committed to a language the caller never asked for.How tested
pnpm test(typecheck + build + vitest coverage)test/platform/language.test.ts— the three result cases, name-vs-code rejection (Korean,한국어,english,xx), ISO 639-2 narrowing (kor→ko), andrequireResolvedLanguage().test/generation/repository-run.test.ts— a rejected request writes no.run.json, an interrupted run stays resumable after a rejected resume, and a retry with a real code succeeds.languageWarning.Breaking change
Callers relying on the silent English fallback for an invalid locale now get an error instead:
openwiki_begin→invalid_input, no run started--language→ exit1Fix: pass a real BCP-47 code (
ko,zh-CN,pt-BR) and rerun.**Changeset is marked
patch.Happy to switch it to
minorif you'd rather signal it in the version. just say the word.