Skip to content

[dotnet-port-fixes] Preserve shelltool UTF-8 head/tail ordering #521

Description

@github-actions

Summary

Aligned Go's internal tool/shelltool head/tail output buffer with the upstream .NET UTF-8 ordering fix from commit 85c00fc55b6f8cef8b88e83f364b527a0d5cd275.

The Go headTailBuffer now seals the head once a full rune no longer fits, keeps all later runes in the tail, and only evicts tail runes after the total captured output exceeds the configured cap. This preserves output order for mixed ASCII and multibyte UTF-8 content while keeping the existing public Go API unchanged.

Ported .NET PRs

Breaking Changes

No.

Tests and Examples

  • Ran go test ./tool/shelltool
  • Added focused parity tests for exact-cap UTF-8 output ordering
  • Added focused parity tests for overflowed UTF-8 output ordering and truncation markers
  • No example changes were needed because this is an internal buffering fix

Notes

  • Upstream commit ported: 85c00fc55b6f8cef8b88e83f364b527a0d5cd275 (https://github.com/microsoft/agent-framework/commit/85c00fc55b6f8cef8b88e83f364b527a0d5cd275)
  • I skipped nearby upstream items such as Fix message ordering in workflow-hosted agents agent-framework#7123 and #7042 because they were already tracked by existing Go porting work, and larger API-shaping changes remained out of scope for this fixes workflow.
  • This PR only changes internal shelltool buffering behavior and test coverage.

Generated by .NET to Go Fixes and Test Porting Agent · 431.5 AIC · ⌖ 45.2 AIC · ⊞ 21.7K ·


Note

This was originally intended as a pull request, but GitHub Actions is not permitted to create or approve pull requests in this repository.
The changes have been pushed to branch copilot/dotnet-port-fixes-headtailbuffer-utf8-order-20260717-7623eefee6ec62c1.

Click here to create the pull request

To fix the permissions issue, go to SettingsActionsGeneral and enable Allow GitHub Actions to create and approve pull requests. See also: gh-aw FAQ

Show patch preview (123 of 123 lines)
From 1c6a549768436bfd198a916fad3b2aae139974be Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Fri, 17 Jul 2026 03:37:23 +0000
Subject: [PATCH] shelltool: preserve head/tail UTF-8 ordering

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 tool/shelltool/localshell.go               | 14 ++++---
 tool/shelltool/localshell_internal_test.go | 49 +++++++++++++++++++++-
 2 files changed, 57 insertions(+), 6 deletions(-)

diff --git a/tool/shelltool/localshell.go b/tool/shelltool/localshell.go
index 3fdc90636..82df2e956 100644
--- a/tool/shelltool/localshell.go
+++ b/tool/shelltool/localshell.go
@@ -398,14 +398,17 @@ func runStateless(ctx context.Context, opts LocalConfig, command string) (Result
 // --------------------------------------------------------------------------
 
 // headTailBuffer keeps up to cap bytes: the first half as head and the most
-// recent half as a rolling tail. When the total exceeds cap, the middle is
-// dropped and [Result.Truncated] is set. This mirrors the .NET HeadTailBuffer.
+// recent half as a rolling tail. Once a complete rune no longer fits in the
+// head, it and all later runes stay in the tail so the final output keeps the
+// original UTF-8 ordering. When the total exceeds cap, the middle is dropped
+// and [Result.Truncated] is set. This mirrors the .NET HeadTailBuffer.
 type headTailBuffer struct {
 	cap        int
 	head       []byte
 	tail       [][]byte // queue of complete rune-byte slices
 	tailBytes  int
 	totalBytes int
+	headSealed bool
 	truncated  bool
 }
 
@@ -430,15 +433,16 @@ func (b *headTailBuffer) Write(p []byte) (int, error) {
 
 		b.totalBytes += size
 
-		if len(b.head)+len(encoded) <= headCap {
+		if !b.headSealed && len(b.head)+len(encoded) <= headCap {
 			b.head = append(b.head, encoded...)
 			continue
 		}
-		// Head full — append to tail.
+		// Once a complete rune cannot fit in the head, all later runes stay in the tail.
+	
... (truncated)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions