Description of the Change
Problem
templates.ValidatePattern builds a regexp from the tool-call-ID template and compiles it with regexp.MustCompile. The {r:N:cs} length is read with strconv.Atoi and the error is discarded. Go's regexp refuses a repeat count above 1000, so a template such as call_{r:1001:x}, or one whose count overflows int, panics.
The template is taken verbatim from the model's submit_pattern tool call in detectPatternWithAI (backend/pkg/providers/provider/agents.go). The panic happens inside go fc.prepareFlow(...), where no recover runs, so one such answer stops the whole process and every running flow with it. A user with settings.prompts.edit can cause this by editing the tool_call_id_detector prompt, and an OpenAI-compatible endpoint can cause it by answering with that template or with IDs longer than 1000 characters.
The heuristic fallback has the same gap: it derives {r:N:cs} from the sample IDs and stores it unvalidated. The template is written to flows.tool_call_id_template, and on restart restoreChain → NormalizeToolCallIDs → ValidatePattern panics again for every tool call in the chain.
Solution
parsePatternreturns an error when the length does not parse or exceeds the regexp limit (1000).ValidatePatternreturns that error and compiles withregexp.Compileinstead ofMustCompile.GenerateFromPatternkeeps its no-error contract and falls back toDefaultToolCallIDTemplatefor a pattern that does not compile.cast.ToolCallIDTemplatenow points at that constant.fallbackHeuristicDetectionyields""for a pattern it cannot compile, so the caller takes the default.toolCallIDTemplateOrDefaultreplaces a stored template that no longer compiles with the default, which also repairs rows written before this change.templates.ValidatePatternSyntaxis the one exported check the last two use.
Reproduction of the panic before the fix (Go 1.24.7):
{r:1000:x} -> ok
{r:1001:x} -> PANIC: regexp: Compile(`^call_[a-zA-Z0-9]{1001}$`): invalid repeat count
{r:99999999999999999999:x} -> PANIC (Atoi overflow yields 9223372036854775807)Type of Change
- 🐛 Bug fix (non-breaking change which fixes an issue)
- 🛡️ Security update
Areas Affected
- Core Services (Frontend UI/Backend API)
- AI Agents (Researcher/Developer/Executor)
- External Integrations (LLM/Search APIs)
Testing and Verification
Test Steps
cd backend && go test -race -count=1 ./pkg/templates/ ./pkg/cast/ ./pkg/providers/provider/ ./pkg/providers/go vet,gofmt -landgolangci-lint run(v2.12.2, the CI pin) on the same packages.- Mutation proof in a throwaway copy: with the length cap removed, the Atoi error ignored, the heuristic syntax check removed, or the stored-template check removed, the corresponding new test fails.
Test Results
New tests: two rows in TestTemplates_ValidatePattern_NamesWhereASampleDiverges, TestTemplates_ValidatePatternSyntax_StopsAtTheLargestRepeatTheRegexpCompiles, TestTemplates_GenerateFromPattern_FallsBackToTheDefaultTemplateWhenThePatternCannotCompile, a row in TestAgents_FallbackHeuristicDetection_BuildsThePatternTheSamplesShare and a row in TestHelpers_ToolCallIDTemplateOrDefault_NeverYieldsAnEmptyTemplate.
pkg/templates, pkg/cast, pkg/providers/provider and pkg/providers pass under -race; golangci-lint reports 0 issues. The one exception: TestPerformer_AiResponsePart_ReplaysATextlessThinkingTurnOnTheClaudeDoors/bedrock/* fails on unmodified main in my environment too (unable to add custom RootCAs HTTPClient, an AWS SDK config error caused by the sandbox's HTTP proxy) and was skipped. No dependency changed, so go mod tidy leaves go.mod and go.sum as they are.
Security Considerations
Removes a remote denial of service: a prompt edit by a low-privilege user, or a hostile LLM endpoint, could crash the server and leave a flow that crashes it again on every restart. No new dependencies, no permission changes.
Performance Impact
None. One extra parse of the template on flow and assistant creation.
Deployment Notes
None. A flow whose stored template no longer compiles falls back to call_{r:24:x} on load and its chain IDs are normalised to it.
Additional Notes
A recover in the flow prepare and worker goroutines would turn any future panic into a failed flow instead of a dead process. It is left out of this PR to keep the change to the bug.