Skip to content

fix(backend): remove initialPrompt skip that blocked display name fallback#1562

Open
quay-devel wants to merge 9 commits into
ambient-code:mainfrom
quay-devel:bugfix/issue-1561-display-name-fallback
Open

fix(backend): remove initialPrompt skip that blocked display name fallback#1562
quay-devel wants to merge 9 commits into
ambient-code:mainfrom
quay-devel:bugfix/issue-1561-display-name-fallback

Conversation

@quay-devel
Copy link
Copy Markdown
Contributor

@quay-devel quay-devel commented May 12, 2026

Summary

  • Removed the 5-line initialPrompt skip guard in agui_proxy.go:1005-1009 that prevented the AG-UI proxy from acting as a fallback when CreateSession's async display name generation failed
  • Updated the comment in sessions.go to document the new fallback relationship between the two generation paths
  • Added 4 regression tests for triggerDisplayNameGenerationIfNeeded covering the key code paths

Problem

Sessions intermittently showed raw session IDs (session-<UUID>) instead of friendly display names. The display name system has two generation paths:

  • Path A (CreateSession handler): fires GenerateDisplayNameAsync in a goroutine with 10s timeout
  • Path B (AG-UI proxy): generates on first /agui/run POST

Path B intentionally skipped messages matching initialPrompt to prevent double-generation. But this also blocked Path B from acting as a fallback when Path A failed (API timeout, Vertex errors, missing credentials). The only recovery was the user sending a second message.

Fix

The initialPrompt skip was redundant — two existing guards already prevent double-generation:

  1. ShouldGenerateDisplayName(spec) returns false if displayName is already set
  2. updateSessionDisplayNameInternal has a first-write-wins race guard at display_name.go:302-307

Removing the skip lets Path B naturally retry when Path A fails, with no risk of overwrites. Worst case in a race: one redundant Haiku API call (~$0.001).

Test plan

  • All 26 websocket package tests pass (including 4 new regression tests)
  • gofmt and go vet clean
  • Verify in staging: create sessions with initialPrompt, confirm display name appears even when Vertex API is slow/unavailable

Summary by CodeRabbit

  • Bug Fixes

    • Improved display name generation to be more consistent and reliable when creating sessions with initial prompts.
  • Tests

    • Added comprehensive test coverage for display name generation scenarios, including edge cases.

…lback

Sessions intermittently showed raw session IDs instead of friendly display
names. The AG-UI proxy's triggerDisplayNameGenerationIfNeeded skipped messages
matching initialPrompt (agui_proxy.go:1005-1009), which was intended to
prevent double-generation since CreateSession already fires
GenerateDisplayNameAsync for initialPrompt sessions. However, this also
blocked the fallback path: when CreateSession's async generation failed
(API timeout, Vertex errors), Path B in the AG-UI proxy couldn't retry
because it skipped the very message it needed to generate from.

The skip was redundant — two existing guards already prevent
double-generation:
1. ShouldGenerateDisplayName() returns false if displayName is already set
2. updateSessionDisplayNameInternal() has a first-write-wins race guard

Removing the skip lets the AG-UI proxy naturally retry display name
generation when the runner POSTs the initial prompt to /agui/run, without
risk of overwrites.

Includes 4 regression tests for triggerDisplayNameGenerationIfNeeded
covering: initialPrompt not skipped, skip when name already set, skip
when no user message, and skip when DynamicClient is nil.

## Test plan
- [x] All 26 websocket package tests pass
- [x] gofmt and go vet clean
- [ ] Verify in staging: create sessions with initialPrompt, confirm
      display name appears even when Vertex API is slow/unavailable

Fixes ambient-code#1561

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

📝 Walkthrough

Walkthrough

Display-name generation now triggers consistently on first AG-UI proxy interaction without skipping when the user message matches initialPrompt. GenerateDisplayNameAsync converted to a package-level function variable for test mockability, and regression tests validate the corrected behavior.

Changes

Display-Name Generation Trigger Fix

Layer / File(s) Summary
Testable function variable
components/backend/handlers/display_name.go
GenerateDisplayNameAsync declared as a package-level function variable instead of direct function, enabling test overrides for mock injection.
Remove initialPrompt skip condition
components/backend/websocket/agui_proxy.go, components/backend/handlers/sessions.go
triggerDisplayNameGenerationIfNeeded no longer skips generation when the first user message matches session's initialPrompt. Updated comment clarifies AG-UI fallback behavior on /agui/run.
Regression test coverage
components/backend/websocket/agui_proxy_test.go
Four new test cases validate: generation triggers when displayName is empty and user message exists, skipping when displayName already set, skipping when no user message, and safe nil-check on handlers.DynamicClient. Includes helper setup that temporarily overrides global handler dependencies.

Possibly Related Issues

🚥 Pre-merge checks | ✅ 7 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (7 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title follows Conventional Commits format (fix(backend): ...) and accurately describes the main change: removing a problematic initialPrompt skip that blocked display name fallback.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Performance And Algorithmic Complexity ✅ Passed No performance regressions. Skip removal safe—guards prevent duplicates. One goroutine/request, 10s timeout.
Security And Secret Handling ✅ Passed PR contains no security or secret handling violations. Credentials properly protected, APIs authenticated, injection mitigated, sensitive data unexposed, K8s resources unmodified.
Kubernetes Resource Safety ✅ Passed PR modifies only Go backend HTTP handlers. No Kubernetes manifests, resource definitions, RBAC, or child resource creation is changed. Check not applicable.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@netlify
Copy link
Copy Markdown

netlify Bot commented May 12, 2026

Deploy Preview for cheerful-kitten-f556a0 canceled.

Name Link
🔨 Latest commit 6b50c3b
🔍 Latest deploy log https://app.netlify.com/projects/cheerful-kitten-f556a0/deploys/6a0388891478de00085a5bdd

quay-devel and others added 2 commits May 12, 2026 13:12
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Make GenerateDisplayNameAsync a package-level function variable (matching
the existing pattern used for DynamicClient, GetGitHubToken, etc.) so the
regression test can swap it and assert it was actually called.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@quay-devel quay-devel marked this pull request as ready for review May 12, 2026 13:22
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
components/backend/websocket/agui_proxy_test.go (1)

287-338: ⚡ Quick win

Strengthen skip-path tests with explicit non-invocation assertions

The skip cases currently validate outcomes indirectly, so they can still pass if GenerateDisplayNameAsync is called but fails early. Stub handlers.GenerateDisplayNameAsync and assert it is not called in each skip scenario.

Proposed test hardening diff
 func TestTriggerDisplayName_SkipsWhenNameAlreadySet(t *testing.T) {
@@
+	called := false
+	oldFn := handlers.GenerateDisplayNameAsync
+	handlers.GenerateDisplayNameAsync = func(projectName, sessionName, userMessage string, sessionCtx handlers.SessionContext) {
+		called = true
+	}
+	defer func() { handlers.GenerateDisplayNameAsync = oldFn }()
+
 	msgs := []types.Message{
 		{ID: "msg-1", Role: "user", Content: "Help me debug auth"},
 	}
 
 	triggerDisplayNameGenerationIfNeeded("test-project", "test-session", msgs)
+	if called {
+		t.Error("Expected GenerateDisplayNameAsync not to be called when displayName is already set")
+	}
@@
 func TestTriggerDisplayName_SkipsWhenNoUserMessage(t *testing.T) {
@@
+	called := false
+	oldFn := handlers.GenerateDisplayNameAsync
+	handlers.GenerateDisplayNameAsync = func(projectName, sessionName, userMessage string, sessionCtx handlers.SessionContext) {
+		called = true
+	}
+	defer func() { handlers.GenerateDisplayNameAsync = oldFn }()
+
 	// Only assistant messages — no user content to generate from
 	msgs := []types.Message{
 		{ID: "msg-1", Role: "assistant", Content: "I'll help you debug"},
 	}
 
 	triggerDisplayNameGenerationIfNeeded("test-project", "test-session", msgs)
+	if called {
+		t.Error("Expected GenerateDisplayNameAsync not to be called when there is no user message")
+	}
@@
 func TestTriggerDisplayName_SkipsWhenDynamicClientNil(t *testing.T) {
@@
+	called := false
+	oldFn := handlers.GenerateDisplayNameAsync
+	handlers.GenerateDisplayNameAsync = func(projectName, sessionName, userMessage string, sessionCtx handlers.SessionContext) {
+		called = true
+	}
+	defer func() { handlers.GenerateDisplayNameAsync = oldFn }()
+
 	msgs := []types.Message{
 		{ID: "msg-1", Role: "user", Content: "Help me debug auth"},
 	}
 
 	// Should return early without panic when DynamicClient is nil
 	triggerDisplayNameGenerationIfNeeded("test-project", "test-session", msgs)
+	if called {
+		t.Error("Expected GenerateDisplayNameAsync not to be called when DynamicClient is nil")
+	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/backend/websocket/agui_proxy_test.go` around lines 287 - 338, For
each skip-path test (TestTriggerDisplayName_SkipsWhenNameAlreadySet,
TestTriggerDisplayName_SkipsWhenNoUserMessage,
TestTriggerDisplayName_SkipsWhenDynamicClientNil) stub
handlers.GenerateDisplayNameAsync at test start to flip a local "called" flag if
invoked, call triggerDisplayNameGenerationIfNeeded with the existing test
inputs, then assert the "called" flag is false; restore the original
handlers.GenerateDisplayNameAsync and any modified handlers.DynamicClient after
the test so other tests are unaffected; this ensures
triggerDisplayNameGenerationIfNeeded truly avoids invoking
GenerateDisplayNameAsync in those skip scenarios.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@components/backend/websocket/agui_proxy_test.go`:
- Around line 287-338: For each skip-path test
(TestTriggerDisplayName_SkipsWhenNameAlreadySet,
TestTriggerDisplayName_SkipsWhenNoUserMessage,
TestTriggerDisplayName_SkipsWhenDynamicClientNil) stub
handlers.GenerateDisplayNameAsync at test start to flip a local "called" flag if
invoked, call triggerDisplayNameGenerationIfNeeded with the existing test
inputs, then assert the "called" flag is false; restore the original
handlers.GenerateDisplayNameAsync and any modified handlers.DynamicClient after
the test so other tests are unaffected; this ensures
triggerDisplayNameGenerationIfNeeded truly avoids invoking
GenerateDisplayNameAsync in those skip scenarios.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d69edfa7-7b46-4b85-94f9-28521466dd60

📥 Commits

Reviewing files that changed from the base of the PR and between 28874a9 and 8cb172b.

📒 Files selected for processing (4)
  • components/backend/handlers/display_name.go
  • components/backend/handlers/sessions.go
  • components/backend/websocket/agui_proxy.go
  • components/backend/websocket/agui_proxy_test.go
💤 Files with no reviewable changes (1)
  • components/backend/websocket/agui_proxy.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant