* fix(e2e): keep phase log tails UTF-8 safe
The phase runner keeps a bounded in-memory log tail per phase and writes it
to stderr when a phase fails. The byte-based cut can land inside a multi-byte
UTF-8 character, and decoding from that offset emits U+FFFD replacement
characters at the start of the diagnostics tail. Reuse the decodeUtf8Tail
helper from scripts/e2e/lib/text-file-utils.mjs (#109669) so the retained
window starts on a character boundary.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(e2e): declare decodeUtf8Tail in text-file-utils types
phase-runner.ts imports decodeUtf8Tail from text-file-utils.mjs; the
adjacent declaration file must export it or check-test-types fails with
TS2305.
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(e2e): reuse tailText wrapper for phase log tails
Co-Authored-By: Claude <noreply@anthropic.com>
* test(e2e): focus phase tail regression
Co-authored-by: wangmiao0668000666 <wang.miao86@xydigit.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Replace .slice(0, maxChars) with truncateUtf16Safe in two locations:
- readBoundedResponseText (error body truncation)
- truncateBody (issue body preview)
Without this fix, an emoji (or any astral character) straddling the
truncation boundary would leave a lone surrogate in the output, producing
invalid UTF-16 that serializes to a garbled replacement character.
Follows the same pattern as Leon-SK668's PRs #108184, #108193, #108208.
* build(protocol): make @openclaw/gateway-protocol publishable to npm
* chore(mxc): align package version with release train
* fix(release): support gateway-only core packages
* chore(pr): reuse green hosted CI from patch-identical pre-rebase heads (24h window)
* fix(pr): type-safe fixtures and lint cleanup for hosted-CI reuse tests
* feat(sessions): retain reset history in sqlite with physical disk budget
* fix(sessions): satisfy test-types, knip export scan, and docs map
* fix(sessions): align behavioral suites and flip proof with retained history, route-aware cleanup plans
* fix(scripts): bound duplicate PR closure GitHub lookups
The duplicate PR closure helper performs multiple sequential gh API
reads and writes after a merge. Each call used execFileSync without a
timeout, so one stalled GitHub request could block the surrounding
workflow job until it expires.
Add a 60-second timeout and SIGKILL to the default gh runner, applied
to both read-only lookups and pr edit writes that forward stdin.
* test(scripts): add real behavior proof for duplicate PR closure timeout
Add two live subprocess tests using the real spawnSync (not mocked) to
prove the timeout + SIGKILL surface works against actual child
processes:
- A 1 ms timeout against a long-running node subprocess proves
SIGKILL is delivered and the timeout triggers correctly.
- A fast node -e 0 subprocess with the production 60-second bound
proves the timeout does not false-positive on healthy completions.
Switch the vi.mock to a partial mock using importOriginal so
spawnSync remains the real implementation for live tests while
execFileSync stays mocked for the DI-based option assertions.
* refactor(scripts): inject duplicate closure GitHub runner
* fix(scripts): declare trusted tooling pin validator
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(scripts): bound release CI summary GitHub lookups
The release CI summary helper performs multiple sequential gh API reads
during full release validation. Each read used execFileSync without a
timeout, so one stalled GitHub request could block the surrounding
workflow job until it expires.
Route the default gh runner through a caller-owned execFileSync with a
60-second timeout and SIGKILL, and apply the same bound to artifact zip
downloads. The runner accepts an execFileSyncImpl injection so the
timeout contract can be tested without a slow timer-based test.
* test(scripts): add real behavior proof for release CI summary timeout
Add two live subprocess tests alongside the existing mocked tests:
- A 1 ms timeout against a real gh subprocess proves the SIGKILL +
timeout surface works on actual child processes (not only mocks).
- A fast node -e 0 subprocess with the production 60-second bound
proves the timeout does not false-positive on healthy completions.
The runReleaseCiGh runner now accepts an optional timeoutMs parameter
so tests can exercise a shorter timeout without changing the production
default.
* refactor(scripts): share bounded release GitHub runner
* test(scripts): keep release runner assertions type-safe
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>