From c8690ca0d252555f2133a8d11c1053fd12407f1f Mon Sep 17 00:00:00 2001 From: yetone Date: Tue, 6 Oct 2026 21:11:25 +0800 Subject: [PATCH] =?UTF-8?q?gateway:=20a=20Claude=20subscription=20run=20kn?= =?UTF-8?q?ows=20the=20tools=20ToolSearch=20loaded=20before=20its=20agent?= =?UTF-8?q?=20is=20handed=20the=20results=20(TestToolSearchLoadKeepsTheRun?= =?UTF-8?q?=20failed=20on=20CI=20now=20and=20then;=20wizzy-yang's=20#1026?= =?UTF-8?q?=20retried=20the=20assertion)=20=E2=80=94=20on=20the=20continue?= =?UTF-8?q?=20path=20run.continueWith=20answered=20the=20agent's=20MCP=20c?= =?UTF-8?q?all=20before=20run.tools=20recorded=20the=20request's=20tools,?= =?UTF-8?q?=20so=20a=20turn=20that=20finished=20quickly=20could=20be=20she?= =?UTF-8?q?lved=20(shelve=20copies=20run.tools)=20or=20asked=20offers()=20?= =?UTF-8?q?without=20the=20tool=20ToolSearch=20had=20just=20loaded,=20and?= =?UTF-8?q?=20the=20next=20request=20started=20a=20new=20run;=20on=20a=20n?= =?UTF-8?q?ew=20run,=20run.tools=20was=20written=20without=20run.mu=20whil?= =?UTF-8?q?e=20the=20agent=20was=20already=20running.=20The=20tools=20are?= =?UTF-8?q?=20now=20recorded=20under=20run.mu=20before=20continueWith,=20a?= =?UTF-8?q?nd=20a=20new=20run's=20tools=20and=20search=20are=20set=20under?= =?UTF-8?q?=20one=20lock.=20A=2050ms=20sleep=20after=20continueWith=20fail?= =?UTF-8?q?s=20TestToolSearchLoadKeepsTheRun=205/5=20on=20the=20old=20code?= =?UTF-8?q?=20with=20CI's=20message=20and=20passes=20with=20this;=20go=20t?= =?UTF-8?q?est=20-race=20-count=3D100=20-cpu=201,2,8=20-run=20TestToolSear?= =?UTF-8?q?ch=20and=20the=20gateway's=20subscription=20tests=20pass;=20gof?= =?UTF-8?q?mt,=20vet=20pass.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/gateway/claude_subscription.go | 27 ++++++++++++++++--------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/internal/gateway/claude_subscription.go b/internal/gateway/claude_subscription.go index e0c3f311..6900c503 100644 --- a/internal/gateway/claude_subscription.go +++ b/internal/gateway/claude_subscription.go @@ -3018,6 +3018,16 @@ func (s *Server) serveSubscription(w http.ResponseWriter, r *http.Request, from if name == "Claude Code" { err = run.setSafeguards(req) } + // the tools are the run's before its agent is handed them: from + // then on it may answer, finish the turn and be shelved, or be + // asked whether it offers them, before this goroutine goes on + if err == nil && more != nil { + run.mu.Lock() + for _, t := range req.Tools { + run.tools[t.Name] = true + } + run.mu.Unlock() + } if err == nil { events, err = run.continueWith(results, more) } @@ -3026,12 +3036,6 @@ func (s *Server) serveSubscription(w http.ResponseWriter, r *http.Request, from // it ended while it waited: a new one is told the whole // conversation run, how = nil, runExpired - } else if more != nil { - run.mu.Lock() - for _, t := range req.Tools { - run.tools[t.Name] = true - } - run.mu.Unlock() } } // how tool results found the run waiting on them, or why a new one is @@ -3042,15 +3046,18 @@ func (s *Server) serveSubscription(w http.ResponseWriter, r *http.Request, from if run == nil { run, events, err = start(r.Context(), req) if err == nil { - run.tools = map[string]bool{} + // its agent is running already: what it was told is set + // under the run's lock, as offers and shelve read it + tools := map[string]bool{} for _, t := range req.Tools { - run.tools[t.Name] = true + tools[t.Name] = true } + run.mu.Lock() + run.tools = tools if search.Name != "" { - run.mu.Lock() run.search, run.searchName = s.webSearch, search.Name - run.mu.Unlock() } + run.mu.Unlock() } } if err != nil {