Skip to content

fix(amazonq): declare serverInfo so notifications reach the client - #2797

Merged
ashishrp-aws merged 2 commits into
Amazon-Q-Developer:feature/qdev-signup-messagefrom
ashishrp-aws:fix/qdev-signup-serverinfo
Aug 12, 2026
Merged

fix(amazonq): declare serverInfo so notifications reach the client#2797
ashishrp-aws merged 2 commits into
Amazon-Q-Developer:feature/qdev-signup-messagefrom
ashishrp-aws:fix/qdev-signup-serverinfo

Conversation

@ashishrp-aws

Copy link
Copy Markdown
Collaborator

Targets feature/qdev-signup-message, same as #2794 and #2796.

Problem

After #2796 the notification still never reached the client. The runtime only constructs a notification router for servers that declare serverInfo:

if(initializeResult?.serverInfo){this.notificationRouter=newRouterByServerName(initializeResult.serverInfo.name,this.encoding)}

AmazonQServiceServer's initializer returned only capabilities and awsServerCapabilities, so no router was built and showNotification took this branch:

if(!this.notificationRouter){this.logger.log(`Notifications are not supported: serverInfo is not defined`)}this.notificationRouter?.send(...)// no-op

Observed in VS Code against prod RTS with a blocked Builder ID — the block was detected and logged, then silently discarded:

[Warn] lserver: Q Developer plugin access is blocked for this identity: Please visit https://kiro.dev/ ...
lserver: Notifications are not supported: serverInfo is not defined

Two servers in this repo already do this correctly (aws-lsp-identity, aws-lsp-notification); this follows their convention.

Why it took three PRs

Each layer failed silently and independently:

PRLayerFailure mode
#2794detect the denialworked
#2796pass notification to the service manageroptional feature, so omitting it compiled and disabled reporting
thisdeclare serverInfono router, so showNotification is a no-op

Nothing threw at any layer. Worth considering whether showNotification should warn rather than log when it drops a notification — a server author has no way to notice today. Happy to raise that against the runtime separately.

Testing

  • Regression test added asserting the initializer returns serverInfo. The failure mode is silent, so without a test this regresses invisibly.
  • The name is asserted exactly, deliberately: RouterByServerName encodes it into the id of every notification the client echoes back, so renaming it strands followups for notifications already on screen.
  • tsc --noEmit clean, prettier clean, eslint clean (0 errors).
  • Verified end-to-end in VS Code with a server bundle built from this branch installed into the resolved flare directory.

Pre-existing failure, not from this change:amazonQServer.test.ts → "hooks onUpdateConfiguration handler to LSP server" fails on this branch before my change (6 passing/1 failing before, 7/1 after). Left alone as unrelated, but it should be looked at.

Correction to #2796

I claimed there that clients could match on id === 'qDevPluginAccessBlocked'. That is wrong. RouterByServerName.send() replaces the id with base64 of {"serverName":...,"id":...} before it reaches the client, so the raw string never arrives. id is the runtime's followup-routing token, not a semantic identifier. The id is still worth keeping (it is what makes followups routable), but clients cannot match on it as a plain string — both IDE clients currently identify the notification by title, and I'll follow up on a durable way to identify it.

The access-blocked notification still never reached the client after Amazon-Q-Developer#2796. The
runtime only constructs a notification router for servers that declare serverInfo:
if (initializeResult?.serverInfo) {
this.notificationRouter = new RouterByServerName(initializeResult.serverInfo.name, ...)
}
AmazonQServiceServer returned only capabilities and awsServerCapabilities, so the
router was never built and notification.showNotification() logged "Notifications are
not supported: serverInfo is not defined" and dropped the notification. Observed in
VS Code: the block was detected and logged, then silently discarded.
This is the last piece. With Amazon-Q-Developer#2794 (detect), Amazon-Q-Developer#2796 (wire) and this change (deliver),
a blocked identity produces a notification the client can act on.
Added a regression test, because the failure mode is silent: nothing throws and only
a debug line marks the loss. The test asserts the exact name, which is deliberate --
the name is encoded into the id of every notification the client echoes back, so
renaming it strands followups for notifications already on screen.
Note: amazonQServer.test.ts has one pre-existing failure on this branch,
"hooks onUpdateConfiguration handler to LSP server", present before this change
(6 passing/1 failing before, 7 passing/1 failing after). Left alone as unrelated.
@codecov-commenter

codecov-commenter commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.76812% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 62.30%. Comparing base (b0018f8) to head (c55b330).

Files with missing linesPatch %Lines
...codewhisperer/src/shared/streamingClientService.ts58.82%14 Missing ⚠️
.../aws-lsp-codewhisperer/src/shared/amazonQServer.ts0.00%8 Missing ⚠️
...mazonQServiceManager/AmazonQTokenServiceManager.ts88.88%3 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## feature/qdev-signup-message #2797 +/- ##
===============================================================
+ Coverage 60.47% 62.30% +1.83% 
===============================================================
Files 282 282 Lines 71465 71530 +65 Branches 4608 4827 +219 ===============================================================
+ Hits 43218 44570 +1352 + Misses 28158 26868 -1290 - Partials 89 92 +3 
FlagCoverage Δ
unittests62.30% <63.76%> (+1.83%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The observer added in Amazon-Q-Developer#2794 was only on the token client. Chat runs through the
streaming client, so the one surface where a blocked identity actually shows up to the
user was the one place nothing was watching. Detection happened to work anyway because
the gate denies every operation and the A/B config fetch goes through the token client
moments after credentials arrive -- but that is incidental, not a guarantee.
Mirrors the token client exactly: middleware on the outermost initialize step so it
fires once per operation after retries are exhausted, the observer is called inside its
own try/catch, and the error is always rethrown so callers behave as before.
The notifier is now created once per service generation and shared by both clients
rather than created per client. The notifier dedupes per instance, so sharing is what
keeps a blocked identity to a single notification no matter which client sees it first.
It is cleared by resetCodewhispererService, so signing out and back in with another
blocked identity notifies again instead of being suppressed.
Scoped to StreamingClientServiceToken. The IAM variant serves a different surface and
the gate only denies Builder ID, which is bearer-token only.
Pre-existing failures on this branch, unchanged by this commit: utils.test.ts 11
failing (89 passing) and amazonQServer.test.ts 1 failing, both identical before and
after.
@ashishrp-aws

Copy link
Copy Markdown
CollaboratorAuthor

Added a second commit: fix(amazonq): observe access-blocked on the streaming client too.

Why. The observer from #2794 was only on the token client. Chat runs through the streaming client, so the one surface where a blocked identity actually shows up to the user was the one place nothing was watching. Detection worked anyway only because the gate denies every operation and the A/B config fetch goes through the token client moments after credentials arrive — incidental, not a guarantee.

What. Mirrors the token client exactly: middleware on the outermost initialize step (fires once per operation after retries, not per attempt), observer called inside its own try/catch, error always rethrown so callers are unaffected.

Notifier lifetime. The notifier is now created once per service generation and shared by the token and streaming clients rather than created per client. It dedupes per instance, so sharing is what keeps a blocked identity to a single notification regardless of which client observes it first. It is cleared in resetCodewhispererService, so signing out and back in with another blocked identity notifies again instead of being suppressed.

Scope.StreamingClientServiceToken only. The IAM variant serves a different surface, and the gate only denies Builder ID, which is bearer-token only.

Tests. Added one asserting the streaming client receives an observer — previously undefined, which is the gap. Reference-equality against the token client is not assertable in that harness because the token service is a sinon stub, so the test asserts only the streaming side.

Pre-existing failures on this branch, unchanged by either commit (verified by stashing and re-running): utils.test.ts 11 failing / 89 passing, and amazonQServer.test.ts 1 failing. Both identical before and after.

@ashishrp-aws
ashishrp-aws merged commit 13dd9d0 into Amazon-Q-Developer:feature/qdev-signup-messageAug 12, 2026
5 checks passed
ashishrp-aws added a commit that referenced this pull request Aug 12, 2026
#2799)
The serverInfo added in #2797 used one hardcoded name, but
AmazonQServiceServerFactory is instantiated twice -- AmazonQServiceServerIAM and
AmazonQServiceServerToken -- and runtimes including agent-standalone register both. Two
servers reporting the same name makes lspRouter reject initialize outright:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
That fails the whole language server, not just the duplicate. Observed in VS Code as:
Failed to start downloaded LSP, falling back to bundled LSP:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
The client then silently ran its bundled server instead, so Q appeared to work while
none of the access-blocked reporting existed, with only a client-side warning to show
for it.
serverName is now a required parameter rather than a shared constant, since a default
is precisely what let two instantiations collide. The two names are exported so the
uniqueness is assertable, and they must stay stable: the name is encoded into the id of
every notification the client echoes back.
Added a regression test on the distinctness. Verified it bites -- reintroducing the
collision gives 7 passing/2 failing, the fix gives 8 passing/1 failing. No existing
test registers two servers from one runtime, which is why this reached a release.
Pre-existing failure on this branch, unchanged: amazonQServer.test.ts
"hooks onUpdateConfiguration handler to LSP server".
ashishrp-aws added a commit that referenced this pull request Aug 13, 2026
… client (#2800)
* fix(amazonq): pass notification feature to the Q service manager (#2796)
The access-blocked notification added in #2794 never reached the client.
AmazonQServiceServerFactory destructures the features it forwards to the service
manager, and notification was not among them, so features.notification was always
undefined, the guard in serviceFactory never passed, onAccessBlocked was never
assigned, and the notifier could not run.
notification is optional on QServiceManagerFeatures so that existing constructions
and test fixtures keep compiling. That is also why omitting it here did not fail the
build -- it silently disabled client-facing reporting instead. Noted at the call site
so the next person adding a feature there does not repeat it.
Also set a stable id on the notification. Clients need to recognise it without
inspecting its text: the message is the service's own copy and is expected to change,
and FEATURE_NOT_SUPPORTED is reused across several RTS gates so the reason alone does
not identify this one. Both IDE clients already prefer the id when present and fall
back to matching the title only because the released server does not send one yet.
Verified: tsc clean, prettier clean, 6/6 notifier tests pass, and a server bundle
built from this branch contains the wiring where a bundle from the previous head did
not.
* fix(amazonq): declare serverInfo so notifications reach the client (#2797)
* fix(amazonq): declare serverInfo so notifications reach the client
The access-blocked notification still never reached the client after #2796. The
runtime only constructs a notification router for servers that declare serverInfo:
if (initializeResult?.serverInfo) {
this.notificationRouter = new RouterByServerName(initializeResult.serverInfo.name, ...)
}
AmazonQServiceServer returned only capabilities and awsServerCapabilities, so the
router was never built and notification.showNotification() logged "Notifications are
not supported: serverInfo is not defined" and dropped the notification. Observed in
VS Code: the block was detected and logged, then silently discarded.
This is the last piece. With #2794 (detect), #2796 (wire) and this change (deliver),
a blocked identity produces a notification the client can act on.
Added a regression test, because the failure mode is silent: nothing throws and only
a debug line marks the loss. The test asserts the exact name, which is deliberate --
the name is encoded into the id of every notification the client echoes back, so
renaming it strands followups for notifications already on screen.
Note: amazonQServer.test.ts has one pre-existing failure on this branch,
"hooks onUpdateConfiguration handler to LSP server", present before this change
(6 passing/1 failing before, 7 passing/1 failing after). Left alone as unrelated.
* fix(amazonq): observe access-blocked on the streaming client too
The observer added in #2794 was only on the token client. Chat runs through the
streaming client, so the one surface where a blocked identity actually shows up to the
user was the one place nothing was watching. Detection happened to work anyway because
the gate denies every operation and the A/B config fetch goes through the token client
moments after credentials arrive -- but that is incidental, not a guarantee.
Mirrors the token client exactly: middleware on the outermost initialize step so it
fires once per operation after retries are exhausted, the observer is called inside its
own try/catch, and the error is always rethrown so callers behave as before.
The notifier is now created once per service generation and shared by both clients
rather than created per client. The notifier dedupes per instance, so sharing is what
keeps a blocked identity to a single notification no matter which client sees it first.
It is cleared by resetCodewhispererService, so signing out and back in with another
blocked identity notifies again instead of being suppressed.
Scoped to StreamingClientServiceToken. The IAM variant serves a different surface and
the gate only denies Builder ID, which is bearer-token only.
Pre-existing failures on this branch, unchanged by this commit: utils.test.ts 11
failing (89 passing) and amazonQServer.test.ts 1 failing, both identical before and
after.
* fix(amazonq): give the IAM and token servers distinct serverInfo names (#2799)
The serverInfo added in #2797 used one hardcoded name, but
AmazonQServiceServerFactory is instantiated twice -- AmazonQServiceServerIAM and
AmazonQServiceServerToken -- and runtimes including agent-standalone register both. Two
servers reporting the same name makes lspRouter reject initialize outright:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
That fails the whole language server, not just the duplicate. Observed in VS Code as:
Failed to start downloaded LSP, falling back to bundled LSP:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
The client then silently ran its bundled server instead, so Q appeared to work while
none of the access-blocked reporting existed, with only a client-side warning to show
for it.
serverName is now a required parameter rather than a shared constant, since a default
is precisely what let two instantiations collide. The two names are exported so the
uniqueness is assertable, and they must stay stable: the name is encoded into the id of
every notification the client echoes back.
Added a regression test on the distinctness. Verified it bites -- reintroducing the
collision gives 7 passing/2 failing, the fix gives 8 passing/1 failing. No existing
test registers two servers from one runtime, which is why this reached a release.
Pre-existing failure on this branch, unchanged: amazonQServer.test.ts
"hooks onUpdateConfiguration handler to LSP server".
* test(amazonq): address review findings on the access-blocked observer (#2801)
Three review follow-ups, no behaviour change for users.
Name the streaming client's middleware, matching the token client. Without a name a
second registration stacks another observer rather than replacing the first, which would
report the same block twice, and the middleware is anonymous in SDK stack introspection.
Assert the server-name uniqueness against the real exported servers rather than the two
constants. Comparing constants cannot catch the same name being passed to both factory
calls, which is the mistake that actually shipped. Verified the test bites: making the
names identical fails it (7 passing/2 failing vs 8/1).
Add two tests for the streaming observer. They assert the wiring rather than the callback
because the existing harness stubs CodeWhispererStreaming.prototype.sendMessage, which
bypasses the middleware stack entirely -- a behavioural test there would pass even if the
middleware did not exist.
shared group: 337 passing / 45 failing, against 334 / 45 before, so the 3 new tests and
no new failures.
Sign up for freeto 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.

4 participants

@ashishrp-aws@codecov-commenter@chungjac@hezelin-work
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix(amazonq): declare serverInfo so notifications reach the client by ashishrp-aws · Pull Request #2797 · Amazon-Q-Developer/language-servers · GitHub
Skip to content

fix(amazonq): declare serverInfo so notifications reach the client - #2797

Merged
ashishrp-aws merged 2 commits into
Amazon-Q-Developer:feature/qdev-signup-messagefrom
ashishrp-aws:fix/qdev-signup-serverinfo
Aug 12, 2026
Merged

fix(amazonq): declare serverInfo so notifications reach the client#2797
ashishrp-aws merged 2 commits into
Amazon-Q-Developer:feature/qdev-signup-messagefrom
ashishrp-aws:fix/qdev-signup-serverinfo

Conversation

@ashishrp-aws

Copy link
Copy Markdown
Collaborator

Targets feature/qdev-signup-message, same as #2794 and #2796.

Problem

After #2796 the notification still never reached the client. The runtime only constructs a notification router for servers that declare serverInfo:

if(initializeResult?.serverInfo){this.notificationRouter=newRouterByServerName(initializeResult.serverInfo.name,this.encoding)}

AmazonQServiceServer's initializer returned only capabilities and awsServerCapabilities, so no router was built and showNotification took this branch:

if(!this.notificationRouter){this.logger.log(`Notifications are not supported: serverInfo is not defined`)}this.notificationRouter?.send(...)// no-op

Observed in VS Code against prod RTS with a blocked Builder ID — the block was detected and logged, then silently discarded:

[Warn] lserver: Q Developer plugin access is blocked for this identity: Please visit https://kiro.dev/ ...
lserver: Notifications are not supported: serverInfo is not defined

Two servers in this repo already do this correctly (aws-lsp-identity, aws-lsp-notification); this follows their convention.

Why it took three PRs

Each layer failed silently and independently:

PRLayerFailure mode
#2794detect the denialworked
#2796pass notification to the service manageroptional feature, so omitting it compiled and disabled reporting
thisdeclare serverInfono router, so showNotification is a no-op

Nothing threw at any layer. Worth considering whether showNotification should warn rather than log when it drops a notification — a server author has no way to notice today. Happy to raise that against the runtime separately.

Testing

  • Regression test added asserting the initializer returns serverInfo. The failure mode is silent, so without a test this regresses invisibly.
  • The name is asserted exactly, deliberately: RouterByServerName encodes it into the id of every notification the client echoes back, so renaming it strands followups for notifications already on screen.
  • tsc --noEmit clean, prettier clean, eslint clean (0 errors).
  • Verified end-to-end in VS Code with a server bundle built from this branch installed into the resolved flare directory.

Pre-existing failure, not from this change:amazonQServer.test.ts → "hooks onUpdateConfiguration handler to LSP server" fails on this branch before my change (6 passing/1 failing before, 7/1 after). Left alone as unrelated, but it should be looked at.

Correction to #2796

I claimed there that clients could match on id === 'qDevPluginAccessBlocked'. That is wrong. RouterByServerName.send() replaces the id with base64 of {"serverName":...,"id":...} before it reaches the client, so the raw string never arrives. id is the runtime's followup-routing token, not a semantic identifier. The id is still worth keeping (it is what makes followups routable), but clients cannot match on it as a plain string — both IDE clients currently identify the notification by title, and I'll follow up on a durable way to identify it.

The access-blocked notification still never reached the client after Amazon-Q-Developer#2796. The
runtime only constructs a notification router for servers that declare serverInfo:
if (initializeResult?.serverInfo) {
this.notificationRouter = new RouterByServerName(initializeResult.serverInfo.name, ...)
}
AmazonQServiceServer returned only capabilities and awsServerCapabilities, so the
router was never built and notification.showNotification() logged "Notifications are
not supported: serverInfo is not defined" and dropped the notification. Observed in
VS Code: the block was detected and logged, then silently discarded.
This is the last piece. With Amazon-Q-Developer#2794 (detect), Amazon-Q-Developer#2796 (wire) and this change (deliver),
a blocked identity produces a notification the client can act on.
Added a regression test, because the failure mode is silent: nothing throws and only
a debug line marks the loss. The test asserts the exact name, which is deliberate --
the name is encoded into the id of every notification the client echoes back, so
renaming it strands followups for notifications already on screen.
Note: amazonQServer.test.ts has one pre-existing failure on this branch,
"hooks onUpdateConfiguration handler to LSP server", present before this change
(6 passing/1 failing before, 7 passing/1 failing after). Left alone as unrelated.
@codecov-commenter

codecov-commenter commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.76812% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 62.30%. Comparing base (b0018f8) to head (c55b330).

Files with missing linesPatch %Lines
...codewhisperer/src/shared/streamingClientService.ts58.82%14 Missing ⚠️
.../aws-lsp-codewhisperer/src/shared/amazonQServer.ts0.00%8 Missing ⚠️
...mazonQServiceManager/AmazonQTokenServiceManager.ts88.88%3 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## feature/qdev-signup-message #2797 +/- ##
===============================================================
+ Coverage 60.47% 62.30% +1.83% 
===============================================================
Files 282 282 Lines 71465 71530 +65 Branches 4608 4827 +219 ===============================================================
+ Hits 43218 44570 +1352 + Misses 28158 26868 -1290 - Partials 89 92 +3 
FlagCoverage Δ
unittests62.30% <63.76%> (+1.83%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The observer added in Amazon-Q-Developer#2794 was only on the token client. Chat runs through the
streaming client, so the one surface where a blocked identity actually shows up to the
user was the one place nothing was watching. Detection happened to work anyway because
the gate denies every operation and the A/B config fetch goes through the token client
moments after credentials arrive -- but that is incidental, not a guarantee.
Mirrors the token client exactly: middleware on the outermost initialize step so it
fires once per operation after retries are exhausted, the observer is called inside its
own try/catch, and the error is always rethrown so callers behave as before.
The notifier is now created once per service generation and shared by both clients
rather than created per client. The notifier dedupes per instance, so sharing is what
keeps a blocked identity to a single notification no matter which client sees it first.
It is cleared by resetCodewhispererService, so signing out and back in with another
blocked identity notifies again instead of being suppressed.
Scoped to StreamingClientServiceToken. The IAM variant serves a different surface and
the gate only denies Builder ID, which is bearer-token only.
Pre-existing failures on this branch, unchanged by this commit: utils.test.ts 11
failing (89 passing) and amazonQServer.test.ts 1 failing, both identical before and
after.
@ashishrp-aws

Copy link
Copy Markdown
CollaboratorAuthor

Added a second commit: fix(amazonq): observe access-blocked on the streaming client too.

Why. The observer from #2794 was only on the token client. Chat runs through the streaming client, so the one surface where a blocked identity actually shows up to the user was the one place nothing was watching. Detection worked anyway only because the gate denies every operation and the A/B config fetch goes through the token client moments after credentials arrive — incidental, not a guarantee.

What. Mirrors the token client exactly: middleware on the outermost initialize step (fires once per operation after retries, not per attempt), observer called inside its own try/catch, error always rethrown so callers are unaffected.

Notifier lifetime. The notifier is now created once per service generation and shared by the token and streaming clients rather than created per client. It dedupes per instance, so sharing is what keeps a blocked identity to a single notification regardless of which client observes it first. It is cleared in resetCodewhispererService, so signing out and back in with another blocked identity notifies again instead of being suppressed.

Scope.StreamingClientServiceToken only. The IAM variant serves a different surface, and the gate only denies Builder ID, which is bearer-token only.

Tests. Added one asserting the streaming client receives an observer — previously undefined, which is the gap. Reference-equality against the token client is not assertable in that harness because the token service is a sinon stub, so the test asserts only the streaming side.

Pre-existing failures on this branch, unchanged by either commit (verified by stashing and re-running): utils.test.ts 11 failing / 89 passing, and amazonQServer.test.ts 1 failing. Both identical before and after.

@ashishrp-aws
ashishrp-aws merged commit 13dd9d0 into Amazon-Q-Developer:feature/qdev-signup-messageAug 12, 2026
5 checks passed
ashishrp-aws added a commit that referenced this pull request Aug 12, 2026
#2799)
The serverInfo added in #2797 used one hardcoded name, but
AmazonQServiceServerFactory is instantiated twice -- AmazonQServiceServerIAM and
AmazonQServiceServerToken -- and runtimes including agent-standalone register both. Two
servers reporting the same name makes lspRouter reject initialize outright:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
That fails the whole language server, not just the duplicate. Observed in VS Code as:
Failed to start downloaded LSP, falling back to bundled LSP:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
The client then silently ran its bundled server instead, so Q appeared to work while
none of the access-blocked reporting existed, with only a client-side warning to show
for it.
serverName is now a required parameter rather than a shared constant, since a default
is precisely what let two instantiations collide. The two names are exported so the
uniqueness is assertable, and they must stay stable: the name is encoded into the id of
every notification the client echoes back.
Added a regression test on the distinctness. Verified it bites -- reintroducing the
collision gives 7 passing/2 failing, the fix gives 8 passing/1 failing. No existing
test registers two servers from one runtime, which is why this reached a release.
Pre-existing failure on this branch, unchanged: amazonQServer.test.ts
"hooks onUpdateConfiguration handler to LSP server".
ashishrp-aws added a commit that referenced this pull request Aug 13, 2026
… client (#2800)
* fix(amazonq): pass notification feature to the Q service manager (#2796)
The access-blocked notification added in #2794 never reached the client.
AmazonQServiceServerFactory destructures the features it forwards to the service
manager, and notification was not among them, so features.notification was always
undefined, the guard in serviceFactory never passed, onAccessBlocked was never
assigned, and the notifier could not run.
notification is optional on QServiceManagerFeatures so that existing constructions
and test fixtures keep compiling. That is also why omitting it here did not fail the
build -- it silently disabled client-facing reporting instead. Noted at the call site
so the next person adding a feature there does not repeat it.
Also set a stable id on the notification. Clients need to recognise it without
inspecting its text: the message is the service's own copy and is expected to change,
and FEATURE_NOT_SUPPORTED is reused across several RTS gates so the reason alone does
not identify this one. Both IDE clients already prefer the id when present and fall
back to matching the title only because the released server does not send one yet.
Verified: tsc clean, prettier clean, 6/6 notifier tests pass, and a server bundle
built from this branch contains the wiring where a bundle from the previous head did
not.
* fix(amazonq): declare serverInfo so notifications reach the client (#2797)
* fix(amazonq): declare serverInfo so notifications reach the client
The access-blocked notification still never reached the client after #2796. The
runtime only constructs a notification router for servers that declare serverInfo:
if (initializeResult?.serverInfo) {
this.notificationRouter = new RouterByServerName(initializeResult.serverInfo.name, ...)
}
AmazonQServiceServer returned only capabilities and awsServerCapabilities, so the
router was never built and notification.showNotification() logged "Notifications are
not supported: serverInfo is not defined" and dropped the notification. Observed in
VS Code: the block was detected and logged, then silently discarded.
This is the last piece. With #2794 (detect), #2796 (wire) and this change (deliver),
a blocked identity produces a notification the client can act on.
Added a regression test, because the failure mode is silent: nothing throws and only
a debug line marks the loss. The test asserts the exact name, which is deliberate --
the name is encoded into the id of every notification the client echoes back, so
renaming it strands followups for notifications already on screen.
Note: amazonQServer.test.ts has one pre-existing failure on this branch,
"hooks onUpdateConfiguration handler to LSP server", present before this change
(6 passing/1 failing before, 7 passing/1 failing after). Left alone as unrelated.
* fix(amazonq): observe access-blocked on the streaming client too
The observer added in #2794 was only on the token client. Chat runs through the
streaming client, so the one surface where a blocked identity actually shows up to the
user was the one place nothing was watching. Detection happened to work anyway because
the gate denies every operation and the A/B config fetch goes through the token client
moments after credentials arrive -- but that is incidental, not a guarantee.
Mirrors the token client exactly: middleware on the outermost initialize step so it
fires once per operation after retries are exhausted, the observer is called inside its
own try/catch, and the error is always rethrown so callers behave as before.
The notifier is now created once per service generation and shared by both clients
rather than created per client. The notifier dedupes per instance, so sharing is what
keeps a blocked identity to a single notification no matter which client sees it first.
It is cleared by resetCodewhispererService, so signing out and back in with another
blocked identity notifies again instead of being suppressed.
Scoped to StreamingClientServiceToken. The IAM variant serves a different surface and
the gate only denies Builder ID, which is bearer-token only.
Pre-existing failures on this branch, unchanged by this commit: utils.test.ts 11
failing (89 passing) and amazonQServer.test.ts 1 failing, both identical before and
after.
* fix(amazonq): give the IAM and token servers distinct serverInfo names (#2799)
The serverInfo added in #2797 used one hardcoded name, but
AmazonQServiceServerFactory is instantiated twice -- AmazonQServiceServerIAM and
AmazonQServiceServerToken -- and runtimes including agent-standalone register both. Two
servers reporting the same name makes lspRouter reject initialize outright:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
That fails the whole language server, not just the duplicate. Observed in VS Code as:
Failed to start downloaded LSP, falling back to bundled LSP:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
The client then silently ran its bundled server instead, so Q appeared to work while
none of the access-blocked reporting existed, with only a client-side warning to show
for it.
serverName is now a required parameter rather than a shared constant, since a default
is precisely what let two instantiations collide. The two names are exported so the
uniqueness is assertable, and they must stay stable: the name is encoded into the id of
every notification the client echoes back.
Added a regression test on the distinctness. Verified it bites -- reintroducing the
collision gives 7 passing/2 failing, the fix gives 8 passing/1 failing. No existing
test registers two servers from one runtime, which is why this reached a release.
Pre-existing failure on this branch, unchanged: amazonQServer.test.ts
"hooks onUpdateConfiguration handler to LSP server".
* test(amazonq): address review findings on the access-blocked observer (#2801)
Three review follow-ups, no behaviour change for users.
Name the streaming client's middleware, matching the token client. Without a name a
second registration stacks another observer rather than replacing the first, which would
report the same block twice, and the middleware is anonymous in SDK stack introspection.
Assert the server-name uniqueness against the real exported servers rather than the two
constants. Comparing constants cannot catch the same name being passed to both factory
calls, which is the mistake that actually shipped. Verified the test bites: making the
names identical fails it (7 passing/2 failing vs 8/1).
Add two tests for the streaming observer. They assert the wiring rather than the callback
because the existing harness stubs CodeWhispererStreaming.prototype.sendMessage, which
bypasses the middleware stack entirely -- a behavioural test there would pass even if the
middleware did not exist.
shared group: 337 passing / 45 failing, against 334 / 45 before, so the 3 new tests and
no new failures.
Sign up for freeto 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.

4 participants

@ashishrp-aws@codecov-commenter@chungjac@hezelin-work
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(amazonq): declare serverInfo so notifications reach the client by ashishrp-aws · Pull Request #2797 · Amazon-Q-Developer/language-servers · GitHub
Skip to content

fix(amazonq): declare serverInfo so notifications reach the client - #2797

Merged
ashishrp-aws merged 2 commits into
Amazon-Q-Developer:feature/qdev-signup-messagefrom
ashishrp-aws:fix/qdev-signup-serverinfo
Aug 12, 2026
Merged

fix(amazonq): declare serverInfo so notifications reach the client#2797
ashishrp-aws merged 2 commits into
Amazon-Q-Developer:feature/qdev-signup-messagefrom
ashishrp-aws:fix/qdev-signup-serverinfo

Conversation

@ashishrp-aws

Copy link
Copy Markdown
Collaborator

Targets feature/qdev-signup-message, same as #2794 and #2796.

Problem

After #2796 the notification still never reached the client. The runtime only constructs a notification router for servers that declare serverInfo:

if(initializeResult?.serverInfo){this.notificationRouter=newRouterByServerName(initializeResult.serverInfo.name,this.encoding)}

AmazonQServiceServer's initializer returned only capabilities and awsServerCapabilities, so no router was built and showNotification took this branch:

if(!this.notificationRouter){this.logger.log(`Notifications are not supported: serverInfo is not defined`)}this.notificationRouter?.send(...)// no-op

Observed in VS Code against prod RTS with a blocked Builder ID — the block was detected and logged, then silently discarded:

[Warn] lserver: Q Developer plugin access is blocked for this identity: Please visit https://kiro.dev/ ...
lserver: Notifications are not supported: serverInfo is not defined

Two servers in this repo already do this correctly (aws-lsp-identity, aws-lsp-notification); this follows their convention.

Why it took three PRs

Each layer failed silently and independently:

PRLayerFailure mode
#2794detect the denialworked
#2796pass notification to the service manageroptional feature, so omitting it compiled and disabled reporting
thisdeclare serverInfono router, so showNotification is a no-op

Nothing threw at any layer. Worth considering whether showNotification should warn rather than log when it drops a notification — a server author has no way to notice today. Happy to raise that against the runtime separately.

Testing

  • Regression test added asserting the initializer returns serverInfo. The failure mode is silent, so without a test this regresses invisibly.
  • The name is asserted exactly, deliberately: RouterByServerName encodes it into the id of every notification the client echoes back, so renaming it strands followups for notifications already on screen.
  • tsc --noEmit clean, prettier clean, eslint clean (0 errors).
  • Verified end-to-end in VS Code with a server bundle built from this branch installed into the resolved flare directory.

Pre-existing failure, not from this change:amazonQServer.test.ts → "hooks onUpdateConfiguration handler to LSP server" fails on this branch before my change (6 passing/1 failing before, 7/1 after). Left alone as unrelated, but it should be looked at.

Correction to #2796

I claimed there that clients could match on id === 'qDevPluginAccessBlocked'. That is wrong. RouterByServerName.send() replaces the id with base64 of {"serverName":...,"id":...} before it reaches the client, so the raw string never arrives. id is the runtime's followup-routing token, not a semantic identifier. The id is still worth keeping (it is what makes followups routable), but clients cannot match on it as a plain string — both IDE clients currently identify the notification by title, and I'll follow up on a durable way to identify it.

The access-blocked notification still never reached the client after Amazon-Q-Developer#2796. The
runtime only constructs a notification router for servers that declare serverInfo:
if (initializeResult?.serverInfo) {
this.notificationRouter = new RouterByServerName(initializeResult.serverInfo.name, ...)
}
AmazonQServiceServer returned only capabilities and awsServerCapabilities, so the
router was never built and notification.showNotification() logged "Notifications are
not supported: serverInfo is not defined" and dropped the notification. Observed in
VS Code: the block was detected and logged, then silently discarded.
This is the last piece. With Amazon-Q-Developer#2794 (detect), Amazon-Q-Developer#2796 (wire) and this change (deliver),
a blocked identity produces a notification the client can act on.
Added a regression test, because the failure mode is silent: nothing throws and only
a debug line marks the loss. The test asserts the exact name, which is deliberate --
the name is encoded into the id of every notification the client echoes back, so
renaming it strands followups for notifications already on screen.
Note: amazonQServer.test.ts has one pre-existing failure on this branch,
"hooks onUpdateConfiguration handler to LSP server", present before this change
(6 passing/1 failing before, 7 passing/1 failing after). Left alone as unrelated.
@codecov-commenter

codecov-commenter commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.76812% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 62.30%. Comparing base (b0018f8) to head (c55b330).

Files with missing linesPatch %Lines
...codewhisperer/src/shared/streamingClientService.ts58.82%14 Missing ⚠️
.../aws-lsp-codewhisperer/src/shared/amazonQServer.ts0.00%8 Missing ⚠️
...mazonQServiceManager/AmazonQTokenServiceManager.ts88.88%3 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## feature/qdev-signup-message #2797 +/- ##
===============================================================
+ Coverage 60.47% 62.30% +1.83% 
===============================================================
Files 282 282 Lines 71465 71530 +65 Branches 4608 4827 +219 ===============================================================
+ Hits 43218 44570 +1352 + Misses 28158 26868 -1290 - Partials 89 92 +3 
FlagCoverage Δ
unittests62.30% <63.76%> (+1.83%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The observer added in Amazon-Q-Developer#2794 was only on the token client. Chat runs through the
streaming client, so the one surface where a blocked identity actually shows up to the
user was the one place nothing was watching. Detection happened to work anyway because
the gate denies every operation and the A/B config fetch goes through the token client
moments after credentials arrive -- but that is incidental, not a guarantee.
Mirrors the token client exactly: middleware on the outermost initialize step so it
fires once per operation after retries are exhausted, the observer is called inside its
own try/catch, and the error is always rethrown so callers behave as before.
The notifier is now created once per service generation and shared by both clients
rather than created per client. The notifier dedupes per instance, so sharing is what
keeps a blocked identity to a single notification no matter which client sees it first.
It is cleared by resetCodewhispererService, so signing out and back in with another
blocked identity notifies again instead of being suppressed.
Scoped to StreamingClientServiceToken. The IAM variant serves a different surface and
the gate only denies Builder ID, which is bearer-token only.
Pre-existing failures on this branch, unchanged by this commit: utils.test.ts 11
failing (89 passing) and amazonQServer.test.ts 1 failing, both identical before and
after.
@ashishrp-aws

Copy link
Copy Markdown
CollaboratorAuthor

Added a second commit: fix(amazonq): observe access-blocked on the streaming client too.

Why. The observer from #2794 was only on the token client. Chat runs through the streaming client, so the one surface where a blocked identity actually shows up to the user was the one place nothing was watching. Detection worked anyway only because the gate denies every operation and the A/B config fetch goes through the token client moments after credentials arrive — incidental, not a guarantee.

What. Mirrors the token client exactly: middleware on the outermost initialize step (fires once per operation after retries, not per attempt), observer called inside its own try/catch, error always rethrown so callers are unaffected.

Notifier lifetime. The notifier is now created once per service generation and shared by the token and streaming clients rather than created per client. It dedupes per instance, so sharing is what keeps a blocked identity to a single notification regardless of which client observes it first. It is cleared in resetCodewhispererService, so signing out and back in with another blocked identity notifies again instead of being suppressed.

Scope.StreamingClientServiceToken only. The IAM variant serves a different surface, and the gate only denies Builder ID, which is bearer-token only.

Tests. Added one asserting the streaming client receives an observer — previously undefined, which is the gap. Reference-equality against the token client is not assertable in that harness because the token service is a sinon stub, so the test asserts only the streaming side.

Pre-existing failures on this branch, unchanged by either commit (verified by stashing and re-running): utils.test.ts 11 failing / 89 passing, and amazonQServer.test.ts 1 failing. Both identical before and after.

@ashishrp-aws
ashishrp-aws merged commit 13dd9d0 into Amazon-Q-Developer:feature/qdev-signup-messageAug 12, 2026
5 checks passed
ashishrp-aws added a commit that referenced this pull request Aug 12, 2026
#2799)
The serverInfo added in #2797 used one hardcoded name, but
AmazonQServiceServerFactory is instantiated twice -- AmazonQServiceServerIAM and
AmazonQServiceServerToken -- and runtimes including agent-standalone register both. Two
servers reporting the same name makes lspRouter reject initialize outright:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
That fails the whole language server, not just the duplicate. Observed in VS Code as:
Failed to start downloaded LSP, falling back to bundled LSP:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
The client then silently ran its bundled server instead, so Q appeared to work while
none of the access-blocked reporting existed, with only a client-side warning to show
for it.
serverName is now a required parameter rather than a shared constant, since a default
is precisely what let two instantiations collide. The two names are exported so the
uniqueness is assertable, and they must stay stable: the name is encoded into the id of
every notification the client echoes back.
Added a regression test on the distinctness. Verified it bites -- reintroducing the
collision gives 7 passing/2 failing, the fix gives 8 passing/1 failing. No existing
test registers two servers from one runtime, which is why this reached a release.
Pre-existing failure on this branch, unchanged: amazonQServer.test.ts
"hooks onUpdateConfiguration handler to LSP server".
ashishrp-aws added a commit that referenced this pull request Aug 13, 2026
… client (#2800)
* fix(amazonq): pass notification feature to the Q service manager (#2796)
The access-blocked notification added in #2794 never reached the client.
AmazonQServiceServerFactory destructures the features it forwards to the service
manager, and notification was not among them, so features.notification was always
undefined, the guard in serviceFactory never passed, onAccessBlocked was never
assigned, and the notifier could not run.
notification is optional on QServiceManagerFeatures so that existing constructions
and test fixtures keep compiling. That is also why omitting it here did not fail the
build -- it silently disabled client-facing reporting instead. Noted at the call site
so the next person adding a feature there does not repeat it.
Also set a stable id on the notification. Clients need to recognise it without
inspecting its text: the message is the service's own copy and is expected to change,
and FEATURE_NOT_SUPPORTED is reused across several RTS gates so the reason alone does
not identify this one. Both IDE clients already prefer the id when present and fall
back to matching the title only because the released server does not send one yet.
Verified: tsc clean, prettier clean, 6/6 notifier tests pass, and a server bundle
built from this branch contains the wiring where a bundle from the previous head did
not.
* fix(amazonq): declare serverInfo so notifications reach the client (#2797)
* fix(amazonq): declare serverInfo so notifications reach the client
The access-blocked notification still never reached the client after #2796. The
runtime only constructs a notification router for servers that declare serverInfo:
if (initializeResult?.serverInfo) {
this.notificationRouter = new RouterByServerName(initializeResult.serverInfo.name, ...)
}
AmazonQServiceServer returned only capabilities and awsServerCapabilities, so the
router was never built and notification.showNotification() logged "Notifications are
not supported: serverInfo is not defined" and dropped the notification. Observed in
VS Code: the block was detected and logged, then silently discarded.
This is the last piece. With #2794 (detect), #2796 (wire) and this change (deliver),
a blocked identity produces a notification the client can act on.
Added a regression test, because the failure mode is silent: nothing throws and only
a debug line marks the loss. The test asserts the exact name, which is deliberate --
the name is encoded into the id of every notification the client echoes back, so
renaming it strands followups for notifications already on screen.
Note: amazonQServer.test.ts has one pre-existing failure on this branch,
"hooks onUpdateConfiguration handler to LSP server", present before this change
(6 passing/1 failing before, 7 passing/1 failing after). Left alone as unrelated.
* fix(amazonq): observe access-blocked on the streaming client too
The observer added in #2794 was only on the token client. Chat runs through the
streaming client, so the one surface where a blocked identity actually shows up to the
user was the one place nothing was watching. Detection happened to work anyway because
the gate denies every operation and the A/B config fetch goes through the token client
moments after credentials arrive -- but that is incidental, not a guarantee.
Mirrors the token client exactly: middleware on the outermost initialize step so it
fires once per operation after retries are exhausted, the observer is called inside its
own try/catch, and the error is always rethrown so callers behave as before.
The notifier is now created once per service generation and shared by both clients
rather than created per client. The notifier dedupes per instance, so sharing is what
keeps a blocked identity to a single notification no matter which client sees it first.
It is cleared by resetCodewhispererService, so signing out and back in with another
blocked identity notifies again instead of being suppressed.
Scoped to StreamingClientServiceToken. The IAM variant serves a different surface and
the gate only denies Builder ID, which is bearer-token only.
Pre-existing failures on this branch, unchanged by this commit: utils.test.ts 11
failing (89 passing) and amazonQServer.test.ts 1 failing, both identical before and
after.
* fix(amazonq): give the IAM and token servers distinct serverInfo names (#2799)
The serverInfo added in #2797 used one hardcoded name, but
AmazonQServiceServerFactory is instantiated twice -- AmazonQServiceServerIAM and
AmazonQServiceServerToken -- and runtimes including agent-standalone register both. Two
servers reporting the same name makes lspRouter reject initialize outright:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
That fails the whole language server, not just the duplicate. Observed in VS Code as:
Failed to start downloaded LSP, falling back to bundled LSP:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
The client then silently ran its bundled server instead, so Q appeared to work while
none of the access-blocked reporting existed, with only a client-side warning to show
for it.
serverName is now a required parameter rather than a shared constant, since a default
is precisely what let two instantiations collide. The two names are exported so the
uniqueness is assertable, and they must stay stable: the name is encoded into the id of
every notification the client echoes back.
Added a regression test on the distinctness. Verified it bites -- reintroducing the
collision gives 7 passing/2 failing, the fix gives 8 passing/1 failing. No existing
test registers two servers from one runtime, which is why this reached a release.
Pre-existing failure on this branch, unchanged: amazonQServer.test.ts
"hooks onUpdateConfiguration handler to LSP server".
* test(amazonq): address review findings on the access-blocked observer (#2801)
Three review follow-ups, no behaviour change for users.
Name the streaming client's middleware, matching the token client. Without a name a
second registration stacks another observer rather than replacing the first, which would
report the same block twice, and the middleware is anonymous in SDK stack introspection.
Assert the server-name uniqueness against the real exported servers rather than the two
constants. Comparing constants cannot catch the same name being passed to both factory
calls, which is the mistake that actually shipped. Verified the test bites: making the
names identical fails it (7 passing/2 failing vs 8/1).
Add two tests for the streaming observer. They assert the wiring rather than the callback
because the existing harness stubs CodeWhispererStreaming.prototype.sendMessage, which
bypasses the middleware stack entirely -- a behavioural test there would pass even if the
middleware did not exist.
shared group: 337 passing / 45 failing, against 334 / 45 before, so the 3 new tests and
no new failures.
Sign up for freeto 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.

4 participants

@ashishrp-aws@codecov-commenter@chungjac@hezelin-work
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(amazonq): declare serverInfo so notifications reach the client by ashishrp-aws · Pull Request #2797 · Amazon-Q-Developer/language-servers · GitHub
Skip to content

fix(amazonq): declare serverInfo so notifications reach the client - #2797

Merged
ashishrp-aws merged 2 commits into
Amazon-Q-Developer:feature/qdev-signup-messagefrom
ashishrp-aws:fix/qdev-signup-serverinfo
Aug 12, 2026
Merged

fix(amazonq): declare serverInfo so notifications reach the client#2797
ashishrp-aws merged 2 commits into
Amazon-Q-Developer:feature/qdev-signup-messagefrom
ashishrp-aws:fix/qdev-signup-serverinfo

Conversation

@ashishrp-aws

Copy link
Copy Markdown
Collaborator

Targets feature/qdev-signup-message, same as #2794 and #2796.

Problem

After #2796 the notification still never reached the client. The runtime only constructs a notification router for servers that declare serverInfo:

if(initializeResult?.serverInfo){this.notificationRouter=newRouterByServerName(initializeResult.serverInfo.name,this.encoding)}

AmazonQServiceServer's initializer returned only capabilities and awsServerCapabilities, so no router was built and showNotification took this branch:

if(!this.notificationRouter){this.logger.log(`Notifications are not supported: serverInfo is not defined`)}this.notificationRouter?.send(...)// no-op

Observed in VS Code against prod RTS with a blocked Builder ID — the block was detected and logged, then silently discarded:

[Warn] lserver: Q Developer plugin access is blocked for this identity: Please visit https://kiro.dev/ ...
lserver: Notifications are not supported: serverInfo is not defined

Two servers in this repo already do this correctly (aws-lsp-identity, aws-lsp-notification); this follows their convention.

Why it took three PRs

Each layer failed silently and independently:

PRLayerFailure mode
#2794detect the denialworked
#2796pass notification to the service manageroptional feature, so omitting it compiled and disabled reporting
thisdeclare serverInfono router, so showNotification is a no-op

Nothing threw at any layer. Worth considering whether showNotification should warn rather than log when it drops a notification — a server author has no way to notice today. Happy to raise that against the runtime separately.

Testing

  • Regression test added asserting the initializer returns serverInfo. The failure mode is silent, so without a test this regresses invisibly.
  • The name is asserted exactly, deliberately: RouterByServerName encodes it into the id of every notification the client echoes back, so renaming it strands followups for notifications already on screen.
  • tsc --noEmit clean, prettier clean, eslint clean (0 errors).
  • Verified end-to-end in VS Code with a server bundle built from this branch installed into the resolved flare directory.

Pre-existing failure, not from this change:amazonQServer.test.ts → "hooks onUpdateConfiguration handler to LSP server" fails on this branch before my change (6 passing/1 failing before, 7/1 after). Left alone as unrelated, but it should be looked at.

Correction to #2796

I claimed there that clients could match on id === 'qDevPluginAccessBlocked'. That is wrong. RouterByServerName.send() replaces the id with base64 of {"serverName":...,"id":...} before it reaches the client, so the raw string never arrives. id is the runtime's followup-routing token, not a semantic identifier. The id is still worth keeping (it is what makes followups routable), but clients cannot match on it as a plain string — both IDE clients currently identify the notification by title, and I'll follow up on a durable way to identify it.

The access-blocked notification still never reached the client after Amazon-Q-Developer#2796. The
runtime only constructs a notification router for servers that declare serverInfo:
if (initializeResult?.serverInfo) {
this.notificationRouter = new RouterByServerName(initializeResult.serverInfo.name, ...)
}
AmazonQServiceServer returned only capabilities and awsServerCapabilities, so the
router was never built and notification.showNotification() logged "Notifications are
not supported: serverInfo is not defined" and dropped the notification. Observed in
VS Code: the block was detected and logged, then silently discarded.
This is the last piece. With Amazon-Q-Developer#2794 (detect), Amazon-Q-Developer#2796 (wire) and this change (deliver),
a blocked identity produces a notification the client can act on.
Added a regression test, because the failure mode is silent: nothing throws and only
a debug line marks the loss. The test asserts the exact name, which is deliberate --
the name is encoded into the id of every notification the client echoes back, so
renaming it strands followups for notifications already on screen.
Note: amazonQServer.test.ts has one pre-existing failure on this branch,
"hooks onUpdateConfiguration handler to LSP server", present before this change
(6 passing/1 failing before, 7 passing/1 failing after). Left alone as unrelated.
@codecov-commenter

codecov-commenter commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.76812% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 62.30%. Comparing base (b0018f8) to head (c55b330).

Files with missing linesPatch %Lines
...codewhisperer/src/shared/streamingClientService.ts58.82%14 Missing ⚠️
.../aws-lsp-codewhisperer/src/shared/amazonQServer.ts0.00%8 Missing ⚠️
...mazonQServiceManager/AmazonQTokenServiceManager.ts88.88%3 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## feature/qdev-signup-message #2797 +/- ##
===============================================================
+ Coverage 60.47% 62.30% +1.83% 
===============================================================
Files 282 282 Lines 71465 71530 +65 Branches 4608 4827 +219 ===============================================================
+ Hits 43218 44570 +1352 + Misses 28158 26868 -1290 - Partials 89 92 +3 
FlagCoverage Δ
unittests62.30% <63.76%> (+1.83%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The observer added in Amazon-Q-Developer#2794 was only on the token client. Chat runs through the
streaming client, so the one surface where a blocked identity actually shows up to the
user was the one place nothing was watching. Detection happened to work anyway because
the gate denies every operation and the A/B config fetch goes through the token client
moments after credentials arrive -- but that is incidental, not a guarantee.
Mirrors the token client exactly: middleware on the outermost initialize step so it
fires once per operation after retries are exhausted, the observer is called inside its
own try/catch, and the error is always rethrown so callers behave as before.
The notifier is now created once per service generation and shared by both clients
rather than created per client. The notifier dedupes per instance, so sharing is what
keeps a blocked identity to a single notification no matter which client sees it first.
It is cleared by resetCodewhispererService, so signing out and back in with another
blocked identity notifies again instead of being suppressed.
Scoped to StreamingClientServiceToken. The IAM variant serves a different surface and
the gate only denies Builder ID, which is bearer-token only.
Pre-existing failures on this branch, unchanged by this commit: utils.test.ts 11
failing (89 passing) and amazonQServer.test.ts 1 failing, both identical before and
after.
@ashishrp-aws

Copy link
Copy Markdown
CollaboratorAuthor

Added a second commit: fix(amazonq): observe access-blocked on the streaming client too.

Why. The observer from #2794 was only on the token client. Chat runs through the streaming client, so the one surface where a blocked identity actually shows up to the user was the one place nothing was watching. Detection worked anyway only because the gate denies every operation and the A/B config fetch goes through the token client moments after credentials arrive — incidental, not a guarantee.

What. Mirrors the token client exactly: middleware on the outermost initialize step (fires once per operation after retries, not per attempt), observer called inside its own try/catch, error always rethrown so callers are unaffected.

Notifier lifetime. The notifier is now created once per service generation and shared by the token and streaming clients rather than created per client. It dedupes per instance, so sharing is what keeps a blocked identity to a single notification regardless of which client observes it first. It is cleared in resetCodewhispererService, so signing out and back in with another blocked identity notifies again instead of being suppressed.

Scope.StreamingClientServiceToken only. The IAM variant serves a different surface, and the gate only denies Builder ID, which is bearer-token only.

Tests. Added one asserting the streaming client receives an observer — previously undefined, which is the gap. Reference-equality against the token client is not assertable in that harness because the token service is a sinon stub, so the test asserts only the streaming side.

Pre-existing failures on this branch, unchanged by either commit (verified by stashing and re-running): utils.test.ts 11 failing / 89 passing, and amazonQServer.test.ts 1 failing. Both identical before and after.

@ashishrp-aws
ashishrp-aws merged commit 13dd9d0 into Amazon-Q-Developer:feature/qdev-signup-messageAug 12, 2026
5 checks passed
ashishrp-aws added a commit that referenced this pull request Aug 12, 2026
#2799)
The serverInfo added in #2797 used one hardcoded name, but
AmazonQServiceServerFactory is instantiated twice -- AmazonQServiceServerIAM and
AmazonQServiceServerToken -- and runtimes including agent-standalone register both. Two
servers reporting the same name makes lspRouter reject initialize outright:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
That fails the whole language server, not just the duplicate. Observed in VS Code as:
Failed to start downloaded LSP, falling back to bundled LSP:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
The client then silently ran its bundled server instead, so Q appeared to work while
none of the access-blocked reporting existed, with only a client-side warning to show
for it.
serverName is now a required parameter rather than a shared constant, since a default
is precisely what let two instantiations collide. The two names are exported so the
uniqueness is assertable, and they must stay stable: the name is encoded into the id of
every notification the client echoes back.
Added a regression test on the distinctness. Verified it bites -- reintroducing the
collision gives 7 passing/2 failing, the fix gives 8 passing/1 failing. No existing
test registers two servers from one runtime, which is why this reached a release.
Pre-existing failure on this branch, unchanged: amazonQServer.test.ts
"hooks onUpdateConfiguration handler to LSP server".
ashishrp-aws added a commit that referenced this pull request Aug 13, 2026
… client (#2800)
* fix(amazonq): pass notification feature to the Q service manager (#2796)
The access-blocked notification added in #2794 never reached the client.
AmazonQServiceServerFactory destructures the features it forwards to the service
manager, and notification was not among them, so features.notification was always
undefined, the guard in serviceFactory never passed, onAccessBlocked was never
assigned, and the notifier could not run.
notification is optional on QServiceManagerFeatures so that existing constructions
and test fixtures keep compiling. That is also why omitting it here did not fail the
build -- it silently disabled client-facing reporting instead. Noted at the call site
so the next person adding a feature there does not repeat it.
Also set a stable id on the notification. Clients need to recognise it without
inspecting its text: the message is the service's own copy and is expected to change,
and FEATURE_NOT_SUPPORTED is reused across several RTS gates so the reason alone does
not identify this one. Both IDE clients already prefer the id when present and fall
back to matching the title only because the released server does not send one yet.
Verified: tsc clean, prettier clean, 6/6 notifier tests pass, and a server bundle
built from this branch contains the wiring where a bundle from the previous head did
not.
* fix(amazonq): declare serverInfo so notifications reach the client (#2797)
* fix(amazonq): declare serverInfo so notifications reach the client
The access-blocked notification still never reached the client after #2796. The
runtime only constructs a notification router for servers that declare serverInfo:
if (initializeResult?.serverInfo) {
this.notificationRouter = new RouterByServerName(initializeResult.serverInfo.name, ...)
}
AmazonQServiceServer returned only capabilities and awsServerCapabilities, so the
router was never built and notification.showNotification() logged "Notifications are
not supported: serverInfo is not defined" and dropped the notification. Observed in
VS Code: the block was detected and logged, then silently discarded.
This is the last piece. With #2794 (detect), #2796 (wire) and this change (deliver),
a blocked identity produces a notification the client can act on.
Added a regression test, because the failure mode is silent: nothing throws and only
a debug line marks the loss. The test asserts the exact name, which is deliberate --
the name is encoded into the id of every notification the client echoes back, so
renaming it strands followups for notifications already on screen.
Note: amazonQServer.test.ts has one pre-existing failure on this branch,
"hooks onUpdateConfiguration handler to LSP server", present before this change
(6 passing/1 failing before, 7 passing/1 failing after). Left alone as unrelated.
* fix(amazonq): observe access-blocked on the streaming client too
The observer added in #2794 was only on the token client. Chat runs through the
streaming client, so the one surface where a blocked identity actually shows up to the
user was the one place nothing was watching. Detection happened to work anyway because
the gate denies every operation and the A/B config fetch goes through the token client
moments after credentials arrive -- but that is incidental, not a guarantee.
Mirrors the token client exactly: middleware on the outermost initialize step so it
fires once per operation after retries are exhausted, the observer is called inside its
own try/catch, and the error is always rethrown so callers behave as before.
The notifier is now created once per service generation and shared by both clients
rather than created per client. The notifier dedupes per instance, so sharing is what
keeps a blocked identity to a single notification no matter which client sees it first.
It is cleared by resetCodewhispererService, so signing out and back in with another
blocked identity notifies again instead of being suppressed.
Scoped to StreamingClientServiceToken. The IAM variant serves a different surface and
the gate only denies Builder ID, which is bearer-token only.
Pre-existing failures on this branch, unchanged by this commit: utils.test.ts 11
failing (89 passing) and amazonQServer.test.ts 1 failing, both identical before and
after.
* fix(amazonq): give the IAM and token servers distinct serverInfo names (#2799)
The serverInfo added in #2797 used one hardcoded name, but
AmazonQServiceServerFactory is instantiated twice -- AmazonQServiceServerIAM and
AmazonQServiceServerToken -- and runtimes including agent-standalone register both. Two
servers reporting the same name makes lspRouter reject initialize outright:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
That fails the whole language server, not just the duplicate. Observed in VS Code as:
Failed to start downloaded LSP, falling back to bundled LSP:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
The client then silently ran its bundled server instead, so Q appeared to work while
none of the access-blocked reporting existed, with only a client-side warning to show
for it.
serverName is now a required parameter rather than a shared constant, since a default
is precisely what let two instantiations collide. The two names are exported so the
uniqueness is assertable, and they must stay stable: the name is encoded into the id of
every notification the client echoes back.
Added a regression test on the distinctness. Verified it bites -- reintroducing the
collision gives 7 passing/2 failing, the fix gives 8 passing/1 failing. No existing
test registers two servers from one runtime, which is why this reached a release.
Pre-existing failure on this branch, unchanged: amazonQServer.test.ts
"hooks onUpdateConfiguration handler to LSP server".
* test(amazonq): address review findings on the access-blocked observer (#2801)
Three review follow-ups, no behaviour change for users.
Name the streaming client's middleware, matching the token client. Without a name a
second registration stacks another observer rather than replacing the first, which would
report the same block twice, and the middleware is anonymous in SDK stack introspection.
Assert the server-name uniqueness against the real exported servers rather than the two
constants. Comparing constants cannot catch the same name being passed to both factory
calls, which is the mistake that actually shipped. Verified the test bites: making the
names identical fails it (7 passing/2 failing vs 8/1).
Add two tests for the streaming observer. They assert the wiring rather than the callback
because the existing harness stubs CodeWhispererStreaming.prototype.sendMessage, which
bypasses the middleware stack entirely -- a behavioural test there would pass even if the
middleware did not exist.
shared group: 337 passing / 45 failing, against 334 / 45 before, so the 3 new tests and
no new failures.
Sign up for freeto 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.

4 participants

@ashishrp-aws@codecov-commenter@chungjac@hezelin-work
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' fix(amazonq): declare serverInfo so notifications reach the client by ashishrp-aws · Pull Request #2797 · Amazon-Q-Developer/language-servers · GitHub
Skip to content

fix(amazonq): declare serverInfo so notifications reach the client - #2797

Merged
ashishrp-aws merged 2 commits into
Amazon-Q-Developer:feature/qdev-signup-messagefrom
ashishrp-aws:fix/qdev-signup-serverinfo
Aug 12, 2026
Merged

fix(amazonq): declare serverInfo so notifications reach the client#2797
ashishrp-aws merged 2 commits into
Amazon-Q-Developer:feature/qdev-signup-messagefrom
ashishrp-aws:fix/qdev-signup-serverinfo

Conversation

@ashishrp-aws

Copy link
Copy Markdown
Collaborator

Targets feature/qdev-signup-message, same as #2794 and #2796.

Problem

After #2796 the notification still never reached the client. The runtime only constructs a notification router for servers that declare serverInfo:

if(initializeResult?.serverInfo){this.notificationRouter=newRouterByServerName(initializeResult.serverInfo.name,this.encoding)}

AmazonQServiceServer's initializer returned only capabilities and awsServerCapabilities, so no router was built and showNotification took this branch:

if(!this.notificationRouter){this.logger.log(`Notifications are not supported: serverInfo is not defined`)}this.notificationRouter?.send(...)// no-op

Observed in VS Code against prod RTS with a blocked Builder ID — the block was detected and logged, then silently discarded:

[Warn] lserver: Q Developer plugin access is blocked for this identity: Please visit https://kiro.dev/ ...
lserver: Notifications are not supported: serverInfo is not defined

Two servers in this repo already do this correctly (aws-lsp-identity, aws-lsp-notification); this follows their convention.

Why it took three PRs

Each layer failed silently and independently:

PRLayerFailure mode
#2794detect the denialworked
#2796pass notification to the service manageroptional feature, so omitting it compiled and disabled reporting
thisdeclare serverInfono router, so showNotification is a no-op

Nothing threw at any layer. Worth considering whether showNotification should warn rather than log when it drops a notification — a server author has no way to notice today. Happy to raise that against the runtime separately.

Testing

  • Regression test added asserting the initializer returns serverInfo. The failure mode is silent, so without a test this regresses invisibly.
  • The name is asserted exactly, deliberately: RouterByServerName encodes it into the id of every notification the client echoes back, so renaming it strands followups for notifications already on screen.
  • tsc --noEmit clean, prettier clean, eslint clean (0 errors).
  • Verified end-to-end in VS Code with a server bundle built from this branch installed into the resolved flare directory.

Pre-existing failure, not from this change:amazonQServer.test.ts → "hooks onUpdateConfiguration handler to LSP server" fails on this branch before my change (6 passing/1 failing before, 7/1 after). Left alone as unrelated, but it should be looked at.

Correction to #2796

I claimed there that clients could match on id === 'qDevPluginAccessBlocked'. That is wrong. RouterByServerName.send() replaces the id with base64 of {"serverName":...,"id":...} before it reaches the client, so the raw string never arrives. id is the runtime's followup-routing token, not a semantic identifier. The id is still worth keeping (it is what makes followups routable), but clients cannot match on it as a plain string — both IDE clients currently identify the notification by title, and I'll follow up on a durable way to identify it.

The access-blocked notification still never reached the client after Amazon-Q-Developer#2796. The
runtime only constructs a notification router for servers that declare serverInfo:
if (initializeResult?.serverInfo) {
this.notificationRouter = new RouterByServerName(initializeResult.serverInfo.name, ...)
}
AmazonQServiceServer returned only capabilities and awsServerCapabilities, so the
router was never built and notification.showNotification() logged "Notifications are
not supported: serverInfo is not defined" and dropped the notification. Observed in
VS Code: the block was detected and logged, then silently discarded.
This is the last piece. With Amazon-Q-Developer#2794 (detect), Amazon-Q-Developer#2796 (wire) and this change (deliver),
a blocked identity produces a notification the client can act on.
Added a regression test, because the failure mode is silent: nothing throws and only
a debug line marks the loss. The test asserts the exact name, which is deliberate --
the name is encoded into the id of every notification the client echoes back, so
renaming it strands followups for notifications already on screen.
Note: amazonQServer.test.ts has one pre-existing failure on this branch,
"hooks onUpdateConfiguration handler to LSP server", present before this change
(6 passing/1 failing before, 7 passing/1 failing after). Left alone as unrelated.
@codecov-commenter

codecov-commenter commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.76812% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 62.30%. Comparing base (b0018f8) to head (c55b330).

Files with missing linesPatch %Lines
...codewhisperer/src/shared/streamingClientService.ts58.82%14 Missing ⚠️
.../aws-lsp-codewhisperer/src/shared/amazonQServer.ts0.00%8 Missing ⚠️
...mazonQServiceManager/AmazonQTokenServiceManager.ts88.88%3 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## feature/qdev-signup-message #2797 +/- ##
===============================================================
+ Coverage 60.47% 62.30% +1.83% 
===============================================================
Files 282 282 Lines 71465 71530 +65 Branches 4608 4827 +219 ===============================================================
+ Hits 43218 44570 +1352 + Misses 28158 26868 -1290 - Partials 89 92 +3 
FlagCoverage Δ
unittests62.30% <63.76%> (+1.83%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The observer added in Amazon-Q-Developer#2794 was only on the token client. Chat runs through the
streaming client, so the one surface where a blocked identity actually shows up to the
user was the one place nothing was watching. Detection happened to work anyway because
the gate denies every operation and the A/B config fetch goes through the token client
moments after credentials arrive -- but that is incidental, not a guarantee.
Mirrors the token client exactly: middleware on the outermost initialize step so it
fires once per operation after retries are exhausted, the observer is called inside its
own try/catch, and the error is always rethrown so callers behave as before.
The notifier is now created once per service generation and shared by both clients
rather than created per client. The notifier dedupes per instance, so sharing is what
keeps a blocked identity to a single notification no matter which client sees it first.
It is cleared by resetCodewhispererService, so signing out and back in with another
blocked identity notifies again instead of being suppressed.
Scoped to StreamingClientServiceToken. The IAM variant serves a different surface and
the gate only denies Builder ID, which is bearer-token only.
Pre-existing failures on this branch, unchanged by this commit: utils.test.ts 11
failing (89 passing) and amazonQServer.test.ts 1 failing, both identical before and
after.
@ashishrp-aws

Copy link
Copy Markdown
CollaboratorAuthor

Added a second commit: fix(amazonq): observe access-blocked on the streaming client too.

Why. The observer from #2794 was only on the token client. Chat runs through the streaming client, so the one surface where a blocked identity actually shows up to the user was the one place nothing was watching. Detection worked anyway only because the gate denies every operation and the A/B config fetch goes through the token client moments after credentials arrive — incidental, not a guarantee.

What. Mirrors the token client exactly: middleware on the outermost initialize step (fires once per operation after retries, not per attempt), observer called inside its own try/catch, error always rethrown so callers are unaffected.

Notifier lifetime. The notifier is now created once per service generation and shared by the token and streaming clients rather than created per client. It dedupes per instance, so sharing is what keeps a blocked identity to a single notification regardless of which client observes it first. It is cleared in resetCodewhispererService, so signing out and back in with another blocked identity notifies again instead of being suppressed.

Scope.StreamingClientServiceToken only. The IAM variant serves a different surface, and the gate only denies Builder ID, which is bearer-token only.

Tests. Added one asserting the streaming client receives an observer — previously undefined, which is the gap. Reference-equality against the token client is not assertable in that harness because the token service is a sinon stub, so the test asserts only the streaming side.

Pre-existing failures on this branch, unchanged by either commit (verified by stashing and re-running): utils.test.ts 11 failing / 89 passing, and amazonQServer.test.ts 1 failing. Both identical before and after.

@ashishrp-aws
ashishrp-aws merged commit 13dd9d0 into Amazon-Q-Developer:feature/qdev-signup-messageAug 12, 2026
5 checks passed
ashishrp-aws added a commit that referenced this pull request Aug 12, 2026
#2799)
The serverInfo added in #2797 used one hardcoded name, but
AmazonQServiceServerFactory is instantiated twice -- AmazonQServiceServerIAM and
AmazonQServiceServerToken -- and runtimes including agent-standalone register both. Two
servers reporting the same name makes lspRouter reject initialize outright:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
That fails the whole language server, not just the duplicate. Observed in VS Code as:
Failed to start downloaded LSP, falling back to bundled LSP:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
The client then silently ran its bundled server instead, so Q appeared to work while
none of the access-blocked reporting existed, with only a client-side warning to show
for it.
serverName is now a required parameter rather than a shared constant, since a default
is precisely what let two instantiations collide. The two names are exported so the
uniqueness is assertable, and they must stay stable: the name is encoded into the id of
every notification the client echoes back.
Added a regression test on the distinctness. Verified it bites -- reintroducing the
collision gives 7 passing/2 failing, the fix gives 8 passing/1 failing. No existing
test registers two servers from one runtime, which is why this reached a release.
Pre-existing failure on this branch, unchanged: amazonQServer.test.ts
"hooks onUpdateConfiguration handler to LSP server".
ashishrp-aws added a commit that referenced this pull request Aug 13, 2026
… client (#2800)
* fix(amazonq): pass notification feature to the Q service manager (#2796)
The access-blocked notification added in #2794 never reached the client.
AmazonQServiceServerFactory destructures the features it forwards to the service
manager, and notification was not among them, so features.notification was always
undefined, the guard in serviceFactory never passed, onAccessBlocked was never
assigned, and the notifier could not run.
notification is optional on QServiceManagerFeatures so that existing constructions
and test fixtures keep compiling. That is also why omitting it here did not fail the
build -- it silently disabled client-facing reporting instead. Noted at the call site
so the next person adding a feature there does not repeat it.
Also set a stable id on the notification. Clients need to recognise it without
inspecting its text: the message is the service's own copy and is expected to change,
and FEATURE_NOT_SUPPORTED is reused across several RTS gates so the reason alone does
not identify this one. Both IDE clients already prefer the id when present and fall
back to matching the title only because the released server does not send one yet.
Verified: tsc clean, prettier clean, 6/6 notifier tests pass, and a server bundle
built from this branch contains the wiring where a bundle from the previous head did
not.
* fix(amazonq): declare serverInfo so notifications reach the client (#2797)
* fix(amazonq): declare serverInfo so notifications reach the client
The access-blocked notification still never reached the client after #2796. The
runtime only constructs a notification router for servers that declare serverInfo:
if (initializeResult?.serverInfo) {
this.notificationRouter = new RouterByServerName(initializeResult.serverInfo.name, ...)
}
AmazonQServiceServer returned only capabilities and awsServerCapabilities, so the
router was never built and notification.showNotification() logged "Notifications are
not supported: serverInfo is not defined" and dropped the notification. Observed in
VS Code: the block was detected and logged, then silently discarded.
This is the last piece. With #2794 (detect), #2796 (wire) and this change (deliver),
a blocked identity produces a notification the client can act on.
Added a regression test, because the failure mode is silent: nothing throws and only
a debug line marks the loss. The test asserts the exact name, which is deliberate --
the name is encoded into the id of every notification the client echoes back, so
renaming it strands followups for notifications already on screen.
Note: amazonQServer.test.ts has one pre-existing failure on this branch,
"hooks onUpdateConfiguration handler to LSP server", present before this change
(6 passing/1 failing before, 7 passing/1 failing after). Left alone as unrelated.
* fix(amazonq): observe access-blocked on the streaming client too
The observer added in #2794 was only on the token client. Chat runs through the
streaming client, so the one surface where a blocked identity actually shows up to the
user was the one place nothing was watching. Detection happened to work anyway because
the gate denies every operation and the A/B config fetch goes through the token client
moments after credentials arrive -- but that is incidental, not a guarantee.
Mirrors the token client exactly: middleware on the outermost initialize step so it
fires once per operation after retries are exhausted, the observer is called inside its
own try/catch, and the error is always rethrown so callers behave as before.
The notifier is now created once per service generation and shared by both clients
rather than created per client. The notifier dedupes per instance, so sharing is what
keeps a blocked identity to a single notification no matter which client sees it first.
It is cleared by resetCodewhispererService, so signing out and back in with another
blocked identity notifies again instead of being suppressed.
Scoped to StreamingClientServiceToken. The IAM variant serves a different surface and
the gate only denies Builder ID, which is bearer-token only.
Pre-existing failures on this branch, unchanged by this commit: utils.test.ts 11
failing (89 passing) and amazonQServer.test.ts 1 failing, both identical before and
after.
* fix(amazonq): give the IAM and token servers distinct serverInfo names (#2799)
The serverInfo added in #2797 used one hardcoded name, but
AmazonQServiceServerFactory is instantiated twice -- AmazonQServiceServerIAM and
AmazonQServiceServerToken -- and runtimes including agent-standalone register both. Two
servers reporting the same name makes lspRouter reject initialize outright:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
That fails the whole language server, not just the duplicate. Observed in VS Code as:
Failed to start downloaded LSP, falling back to bundled LSP:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
The client then silently ran its bundled server instead, so Q appeared to work while
none of the access-blocked reporting existed, with only a client-side warning to show
for it.
serverName is now a required parameter rather than a shared constant, since a default
is precisely what let two instantiations collide. The two names are exported so the
uniqueness is assertable, and they must stay stable: the name is encoded into the id of
every notification the client echoes back.
Added a regression test on the distinctness. Verified it bites -- reintroducing the
collision gives 7 passing/2 failing, the fix gives 8 passing/1 failing. No existing
test registers two servers from one runtime, which is why this reached a release.
Pre-existing failure on this branch, unchanged: amazonQServer.test.ts
"hooks onUpdateConfiguration handler to LSP server".
* test(amazonq): address review findings on the access-blocked observer (#2801)
Three review follow-ups, no behaviour change for users.
Name the streaming client's middleware, matching the token client. Without a name a
second registration stacks another observer rather than replacing the first, which would
report the same block twice, and the middleware is anonymous in SDK stack introspection.
Assert the server-name uniqueness against the real exported servers rather than the two
constants. Comparing constants cannot catch the same name being passed to both factory
calls, which is the mistake that actually shipped. Verified the test bites: making the
names identical fails it (7 passing/2 failing vs 8/1).
Add two tests for the streaming observer. They assert the wiring rather than the callback
because the existing harness stubs CodeWhispererStreaming.prototype.sendMessage, which
bypasses the middleware stack entirely -- a behavioural test there would pass even if the
middleware did not exist.
shared group: 337 passing / 45 failing, against 334 / 45 before, so the 3 new tests and
no new failures.
Sign up for freeto 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.

4 participants

@ashishrp-aws@codecov-commenter@chungjac@hezelin-work
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(amazonq): declare serverInfo so notifications reach the client by ashishrp-aws · Pull Request #2797 · Amazon-Q-Developer/language-servers · GitHub
Skip to content

fix(amazonq): declare serverInfo so notifications reach the client - #2797

Merged
ashishrp-aws merged 2 commits into
Amazon-Q-Developer:feature/qdev-signup-messagefrom
ashishrp-aws:fix/qdev-signup-serverinfo
Aug 12, 2026
Merged

fix(amazonq): declare serverInfo so notifications reach the client#2797
ashishrp-aws merged 2 commits into
Amazon-Q-Developer:feature/qdev-signup-messagefrom
ashishrp-aws:fix/qdev-signup-serverinfo

Conversation

@ashishrp-aws

Copy link
Copy Markdown
Collaborator

Targets feature/qdev-signup-message, same as #2794 and #2796.

Problem

After #2796 the notification still never reached the client. The runtime only constructs a notification router for servers that declare serverInfo:

if(initializeResult?.serverInfo){this.notificationRouter=newRouterByServerName(initializeResult.serverInfo.name,this.encoding)}

AmazonQServiceServer's initializer returned only capabilities and awsServerCapabilities, so no router was built and showNotification took this branch:

if(!this.notificationRouter){this.logger.log(`Notifications are not supported: serverInfo is not defined`)}this.notificationRouter?.send(...)// no-op

Observed in VS Code against prod RTS with a blocked Builder ID — the block was detected and logged, then silently discarded:

[Warn] lserver: Q Developer plugin access is blocked for this identity: Please visit https://kiro.dev/ ...
lserver: Notifications are not supported: serverInfo is not defined

Two servers in this repo already do this correctly (aws-lsp-identity, aws-lsp-notification); this follows their convention.

Why it took three PRs

Each layer failed silently and independently:

PRLayerFailure mode
#2794detect the denialworked
#2796pass notification to the service manageroptional feature, so omitting it compiled and disabled reporting
thisdeclare serverInfono router, so showNotification is a no-op

Nothing threw at any layer. Worth considering whether showNotification should warn rather than log when it drops a notification — a server author has no way to notice today. Happy to raise that against the runtime separately.

Testing

  • Regression test added asserting the initializer returns serverInfo. The failure mode is silent, so without a test this regresses invisibly.
  • The name is asserted exactly, deliberately: RouterByServerName encodes it into the id of every notification the client echoes back, so renaming it strands followups for notifications already on screen.
  • tsc --noEmit clean, prettier clean, eslint clean (0 errors).
  • Verified end-to-end in VS Code with a server bundle built from this branch installed into the resolved flare directory.

Pre-existing failure, not from this change:amazonQServer.test.ts → "hooks onUpdateConfiguration handler to LSP server" fails on this branch before my change (6 passing/1 failing before, 7/1 after). Left alone as unrelated, but it should be looked at.

Correction to #2796

I claimed there that clients could match on id === 'qDevPluginAccessBlocked'. That is wrong. RouterByServerName.send() replaces the id with base64 of {"serverName":...,"id":...} before it reaches the client, so the raw string never arrives. id is the runtime's followup-routing token, not a semantic identifier. The id is still worth keeping (it is what makes followups routable), but clients cannot match on it as a plain string — both IDE clients currently identify the notification by title, and I'll follow up on a durable way to identify it.

The access-blocked notification still never reached the client after Amazon-Q-Developer#2796. The
runtime only constructs a notification router for servers that declare serverInfo:
if (initializeResult?.serverInfo) {
this.notificationRouter = new RouterByServerName(initializeResult.serverInfo.name, ...)
}
AmazonQServiceServer returned only capabilities and awsServerCapabilities, so the
router was never built and notification.showNotification() logged "Notifications are
not supported: serverInfo is not defined" and dropped the notification. Observed in
VS Code: the block was detected and logged, then silently discarded.
This is the last piece. With Amazon-Q-Developer#2794 (detect), Amazon-Q-Developer#2796 (wire) and this change (deliver),
a blocked identity produces a notification the client can act on.
Added a regression test, because the failure mode is silent: nothing throws and only
a debug line marks the loss. The test asserts the exact name, which is deliberate --
the name is encoded into the id of every notification the client echoes back, so
renaming it strands followups for notifications already on screen.
Note: amazonQServer.test.ts has one pre-existing failure on this branch,
"hooks onUpdateConfiguration handler to LSP server", present before this change
(6 passing/1 failing before, 7 passing/1 failing after). Left alone as unrelated.
@codecov-commenter

codecov-commenter commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.76812% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 62.30%. Comparing base (b0018f8) to head (c55b330).

Files with missing linesPatch %Lines
...codewhisperer/src/shared/streamingClientService.ts58.82%14 Missing ⚠️
.../aws-lsp-codewhisperer/src/shared/amazonQServer.ts0.00%8 Missing ⚠️
...mazonQServiceManager/AmazonQTokenServiceManager.ts88.88%3 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## feature/qdev-signup-message #2797 +/- ##
===============================================================
+ Coverage 60.47% 62.30% +1.83% 
===============================================================
Files 282 282 Lines 71465 71530 +65 Branches 4608 4827 +219 ===============================================================
+ Hits 43218 44570 +1352 + Misses 28158 26868 -1290 - Partials 89 92 +3 
FlagCoverage Δ
unittests62.30% <63.76%> (+1.83%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The observer added in Amazon-Q-Developer#2794 was only on the token client. Chat runs through the
streaming client, so the one surface where a blocked identity actually shows up to the
user was the one place nothing was watching. Detection happened to work anyway because
the gate denies every operation and the A/B config fetch goes through the token client
moments after credentials arrive -- but that is incidental, not a guarantee.
Mirrors the token client exactly: middleware on the outermost initialize step so it
fires once per operation after retries are exhausted, the observer is called inside its
own try/catch, and the error is always rethrown so callers behave as before.
The notifier is now created once per service generation and shared by both clients
rather than created per client. The notifier dedupes per instance, so sharing is what
keeps a blocked identity to a single notification no matter which client sees it first.
It is cleared by resetCodewhispererService, so signing out and back in with another
blocked identity notifies again instead of being suppressed.
Scoped to StreamingClientServiceToken. The IAM variant serves a different surface and
the gate only denies Builder ID, which is bearer-token only.
Pre-existing failures on this branch, unchanged by this commit: utils.test.ts 11
failing (89 passing) and amazonQServer.test.ts 1 failing, both identical before and
after.
@ashishrp-aws

Copy link
Copy Markdown
CollaboratorAuthor

Added a second commit: fix(amazonq): observe access-blocked on the streaming client too.

Why. The observer from #2794 was only on the token client. Chat runs through the streaming client, so the one surface where a blocked identity actually shows up to the user was the one place nothing was watching. Detection worked anyway only because the gate denies every operation and the A/B config fetch goes through the token client moments after credentials arrive — incidental, not a guarantee.

What. Mirrors the token client exactly: middleware on the outermost initialize step (fires once per operation after retries, not per attempt), observer called inside its own try/catch, error always rethrown so callers are unaffected.

Notifier lifetime. The notifier is now created once per service generation and shared by the token and streaming clients rather than created per client. It dedupes per instance, so sharing is what keeps a blocked identity to a single notification regardless of which client observes it first. It is cleared in resetCodewhispererService, so signing out and back in with another blocked identity notifies again instead of being suppressed.

Scope.StreamingClientServiceToken only. The IAM variant serves a different surface, and the gate only denies Builder ID, which is bearer-token only.

Tests. Added one asserting the streaming client receives an observer — previously undefined, which is the gap. Reference-equality against the token client is not assertable in that harness because the token service is a sinon stub, so the test asserts only the streaming side.

Pre-existing failures on this branch, unchanged by either commit (verified by stashing and re-running): utils.test.ts 11 failing / 89 passing, and amazonQServer.test.ts 1 failing. Both identical before and after.

@ashishrp-aws
ashishrp-aws merged commit 13dd9d0 into Amazon-Q-Developer:feature/qdev-signup-messageAug 12, 2026
5 checks passed
ashishrp-aws added a commit that referenced this pull request Aug 12, 2026
#2799)
The serverInfo added in #2797 used one hardcoded name, but
AmazonQServiceServerFactory is instantiated twice -- AmazonQServiceServerIAM and
AmazonQServiceServerToken -- and runtimes including agent-standalone register both. Two
servers reporting the same name makes lspRouter reject initialize outright:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
That fails the whole language server, not just the duplicate. Observed in VS Code as:
Failed to start downloaded LSP, falling back to bundled LSP:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
The client then silently ran its bundled server instead, so Q appeared to work while
none of the access-blocked reporting existed, with only a client-side warning to show
for it.
serverName is now a required parameter rather than a shared constant, since a default
is precisely what let two instantiations collide. The two names are exported so the
uniqueness is assertable, and they must stay stable: the name is encoded into the id of
every notification the client echoes back.
Added a regression test on the distinctness. Verified it bites -- reintroducing the
collision gives 7 passing/2 failing, the fix gives 8 passing/1 failing. No existing
test registers two servers from one runtime, which is why this reached a release.
Pre-existing failure on this branch, unchanged: amazonQServer.test.ts
"hooks onUpdateConfiguration handler to LSP server".
ashishrp-aws added a commit that referenced this pull request Aug 13, 2026
… client (#2800)
* fix(amazonq): pass notification feature to the Q service manager (#2796)
The access-blocked notification added in #2794 never reached the client.
AmazonQServiceServerFactory destructures the features it forwards to the service
manager, and notification was not among them, so features.notification was always
undefined, the guard in serviceFactory never passed, onAccessBlocked was never
assigned, and the notifier could not run.
notification is optional on QServiceManagerFeatures so that existing constructions
and test fixtures keep compiling. That is also why omitting it here did not fail the
build -- it silently disabled client-facing reporting instead. Noted at the call site
so the next person adding a feature there does not repeat it.
Also set a stable id on the notification. Clients need to recognise it without
inspecting its text: the message is the service's own copy and is expected to change,
and FEATURE_NOT_SUPPORTED is reused across several RTS gates so the reason alone does
not identify this one. Both IDE clients already prefer the id when present and fall
back to matching the title only because the released server does not send one yet.
Verified: tsc clean, prettier clean, 6/6 notifier tests pass, and a server bundle
built from this branch contains the wiring where a bundle from the previous head did
not.
* fix(amazonq): declare serverInfo so notifications reach the client (#2797)
* fix(amazonq): declare serverInfo so notifications reach the client
The access-blocked notification still never reached the client after #2796. The
runtime only constructs a notification router for servers that declare serverInfo:
if (initializeResult?.serverInfo) {
this.notificationRouter = new RouterByServerName(initializeResult.serverInfo.name, ...)
}
AmazonQServiceServer returned only capabilities and awsServerCapabilities, so the
router was never built and notification.showNotification() logged "Notifications are
not supported: serverInfo is not defined" and dropped the notification. Observed in
VS Code: the block was detected and logged, then silently discarded.
This is the last piece. With #2794 (detect), #2796 (wire) and this change (deliver),
a blocked identity produces a notification the client can act on.
Added a regression test, because the failure mode is silent: nothing throws and only
a debug line marks the loss. The test asserts the exact name, which is deliberate --
the name is encoded into the id of every notification the client echoes back, so
renaming it strands followups for notifications already on screen.
Note: amazonQServer.test.ts has one pre-existing failure on this branch,
"hooks onUpdateConfiguration handler to LSP server", present before this change
(6 passing/1 failing before, 7 passing/1 failing after). Left alone as unrelated.
* fix(amazonq): observe access-blocked on the streaming client too
The observer added in #2794 was only on the token client. Chat runs through the
streaming client, so the one surface where a blocked identity actually shows up to the
user was the one place nothing was watching. Detection happened to work anyway because
the gate denies every operation and the A/B config fetch goes through the token client
moments after credentials arrive -- but that is incidental, not a guarantee.
Mirrors the token client exactly: middleware on the outermost initialize step so it
fires once per operation after retries are exhausted, the observer is called inside its
own try/catch, and the error is always rethrown so callers behave as before.
The notifier is now created once per service generation and shared by both clients
rather than created per client. The notifier dedupes per instance, so sharing is what
keeps a blocked identity to a single notification no matter which client sees it first.
It is cleared by resetCodewhispererService, so signing out and back in with another
blocked identity notifies again instead of being suppressed.
Scoped to StreamingClientServiceToken. The IAM variant serves a different surface and
the gate only denies Builder ID, which is bearer-token only.
Pre-existing failures on this branch, unchanged by this commit: utils.test.ts 11
failing (89 passing) and amazonQServer.test.ts 1 failing, both identical before and
after.
* fix(amazonq): give the IAM and token servers distinct serverInfo names (#2799)
The serverInfo added in #2797 used one hardcoded name, but
AmazonQServiceServerFactory is instantiated twice -- AmazonQServiceServerIAM and
AmazonQServiceServerToken -- and runtimes including agent-standalone register both. Two
servers reporting the same name makes lspRouter reject initialize outright:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
That fails the whole language server, not just the duplicate. Observed in VS Code as:
Failed to start downloaded LSP, falling back to bundled LSP:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
The client then silently ran its bundled server instead, so Q appeared to work while
none of the access-blocked reporting existed, with only a client-side warning to show
for it.
serverName is now a required parameter rather than a shared constant, since a default
is precisely what let two instantiations collide. The two names are exported so the
uniqueness is assertable, and they must stay stable: the name is encoded into the id of
every notification the client echoes back.
Added a regression test on the distinctness. Verified it bites -- reintroducing the
collision gives 7 passing/2 failing, the fix gives 8 passing/1 failing. No existing
test registers two servers from one runtime, which is why this reached a release.
Pre-existing failure on this branch, unchanged: amazonQServer.test.ts
"hooks onUpdateConfiguration handler to LSP server".
* test(amazonq): address review findings on the access-blocked observer (#2801)
Three review follow-ups, no behaviour change for users.
Name the streaming client's middleware, matching the token client. Without a name a
second registration stacks another observer rather than replacing the first, which would
report the same block twice, and the middleware is anonymous in SDK stack introspection.
Assert the server-name uniqueness against the real exported servers rather than the two
constants. Comparing constants cannot catch the same name being passed to both factory
calls, which is the mistake that actually shipped. Verified the test bites: making the
names identical fails it (7 passing/2 failing vs 8/1).
Add two tests for the streaming observer. They assert the wiring rather than the callback
because the existing harness stubs CodeWhispererStreaming.prototype.sendMessage, which
bypasses the middleware stack entirely -- a behavioural test there would pass even if the
middleware did not exist.
shared group: 337 passing / 45 failing, against 334 / 45 before, so the 3 new tests and
no new failures.
Sign up for freeto 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.

4 participants

@ashishrp-aws@codecov-commenter@chungjac@hezelin-work
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(amazonq): declare serverInfo so notifications reach the client by ashishrp-aws · Pull Request #2797 · Amazon-Q-Developer/language-servers · GitHub
Skip to content

fix(amazonq): declare serverInfo so notifications reach the client - #2797

Merged
ashishrp-aws merged 2 commits into
Amazon-Q-Developer:feature/qdev-signup-messagefrom
ashishrp-aws:fix/qdev-signup-serverinfo
Aug 12, 2026
Merged

fix(amazonq): declare serverInfo so notifications reach the client#2797
ashishrp-aws merged 2 commits into
Amazon-Q-Developer:feature/qdev-signup-messagefrom
ashishrp-aws:fix/qdev-signup-serverinfo

Conversation

@ashishrp-aws

Copy link
Copy Markdown
Collaborator

Targets feature/qdev-signup-message, same as #2794 and #2796.

Problem

After #2796 the notification still never reached the client. The runtime only constructs a notification router for servers that declare serverInfo:

if(initializeResult?.serverInfo){this.notificationRouter=newRouterByServerName(initializeResult.serverInfo.name,this.encoding)}

AmazonQServiceServer's initializer returned only capabilities and awsServerCapabilities, so no router was built and showNotification took this branch:

if(!this.notificationRouter){this.logger.log(`Notifications are not supported: serverInfo is not defined`)}this.notificationRouter?.send(...)// no-op

Observed in VS Code against prod RTS with a blocked Builder ID — the block was detected and logged, then silently discarded:

[Warn] lserver: Q Developer plugin access is blocked for this identity: Please visit https://kiro.dev/ ...
lserver: Notifications are not supported: serverInfo is not defined

Two servers in this repo already do this correctly (aws-lsp-identity, aws-lsp-notification); this follows their convention.

Why it took three PRs

Each layer failed silently and independently:

PRLayerFailure mode
#2794detect the denialworked
#2796pass notification to the service manageroptional feature, so omitting it compiled and disabled reporting
thisdeclare serverInfono router, so showNotification is a no-op

Nothing threw at any layer. Worth considering whether showNotification should warn rather than log when it drops a notification — a server author has no way to notice today. Happy to raise that against the runtime separately.

Testing

  • Regression test added asserting the initializer returns serverInfo. The failure mode is silent, so without a test this regresses invisibly.
  • The name is asserted exactly, deliberately: RouterByServerName encodes it into the id of every notification the client echoes back, so renaming it strands followups for notifications already on screen.
  • tsc --noEmit clean, prettier clean, eslint clean (0 errors).
  • Verified end-to-end in VS Code with a server bundle built from this branch installed into the resolved flare directory.

Pre-existing failure, not from this change:amazonQServer.test.ts → "hooks onUpdateConfiguration handler to LSP server" fails on this branch before my change (6 passing/1 failing before, 7/1 after). Left alone as unrelated, but it should be looked at.

Correction to #2796

I claimed there that clients could match on id === 'qDevPluginAccessBlocked'. That is wrong. RouterByServerName.send() replaces the id with base64 of {"serverName":...,"id":...} before it reaches the client, so the raw string never arrives. id is the runtime's followup-routing token, not a semantic identifier. The id is still worth keeping (it is what makes followups routable), but clients cannot match on it as a plain string — both IDE clients currently identify the notification by title, and I'll follow up on a durable way to identify it.

The access-blocked notification still never reached the client after Amazon-Q-Developer#2796. The
runtime only constructs a notification router for servers that declare serverInfo:
if (initializeResult?.serverInfo) {
this.notificationRouter = new RouterByServerName(initializeResult.serverInfo.name, ...)
}
AmazonQServiceServer returned only capabilities and awsServerCapabilities, so the
router was never built and notification.showNotification() logged "Notifications are
not supported: serverInfo is not defined" and dropped the notification. Observed in
VS Code: the block was detected and logged, then silently discarded.
This is the last piece. With Amazon-Q-Developer#2794 (detect), Amazon-Q-Developer#2796 (wire) and this change (deliver),
a blocked identity produces a notification the client can act on.
Added a regression test, because the failure mode is silent: nothing throws and only
a debug line marks the loss. The test asserts the exact name, which is deliberate --
the name is encoded into the id of every notification the client echoes back, so
renaming it strands followups for notifications already on screen.
Note: amazonQServer.test.ts has one pre-existing failure on this branch,
"hooks onUpdateConfiguration handler to LSP server", present before this change
(6 passing/1 failing before, 7 passing/1 failing after). Left alone as unrelated.
@codecov-commenter

codecov-commenter commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.76812% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 62.30%. Comparing base (b0018f8) to head (c55b330).

Files with missing linesPatch %Lines
...codewhisperer/src/shared/streamingClientService.ts58.82%14 Missing ⚠️
.../aws-lsp-codewhisperer/src/shared/amazonQServer.ts0.00%8 Missing ⚠️
...mazonQServiceManager/AmazonQTokenServiceManager.ts88.88%3 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## feature/qdev-signup-message #2797 +/- ##
===============================================================
+ Coverage 60.47% 62.30% +1.83% 
===============================================================
Files 282 282 Lines 71465 71530 +65 Branches 4608 4827 +219 ===============================================================
+ Hits 43218 44570 +1352 + Misses 28158 26868 -1290 - Partials 89 92 +3 
FlagCoverage Δ
unittests62.30% <63.76%> (+1.83%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The observer added in Amazon-Q-Developer#2794 was only on the token client. Chat runs through the
streaming client, so the one surface where a blocked identity actually shows up to the
user was the one place nothing was watching. Detection happened to work anyway because
the gate denies every operation and the A/B config fetch goes through the token client
moments after credentials arrive -- but that is incidental, not a guarantee.
Mirrors the token client exactly: middleware on the outermost initialize step so it
fires once per operation after retries are exhausted, the observer is called inside its
own try/catch, and the error is always rethrown so callers behave as before.
The notifier is now created once per service generation and shared by both clients
rather than created per client. The notifier dedupes per instance, so sharing is what
keeps a blocked identity to a single notification no matter which client sees it first.
It is cleared by resetCodewhispererService, so signing out and back in with another
blocked identity notifies again instead of being suppressed.
Scoped to StreamingClientServiceToken. The IAM variant serves a different surface and
the gate only denies Builder ID, which is bearer-token only.
Pre-existing failures on this branch, unchanged by this commit: utils.test.ts 11
failing (89 passing) and amazonQServer.test.ts 1 failing, both identical before and
after.
@ashishrp-aws

Copy link
Copy Markdown
CollaboratorAuthor

Added a second commit: fix(amazonq): observe access-blocked on the streaming client too.

Why. The observer from #2794 was only on the token client. Chat runs through the streaming client, so the one surface where a blocked identity actually shows up to the user was the one place nothing was watching. Detection worked anyway only because the gate denies every operation and the A/B config fetch goes through the token client moments after credentials arrive — incidental, not a guarantee.

What. Mirrors the token client exactly: middleware on the outermost initialize step (fires once per operation after retries, not per attempt), observer called inside its own try/catch, error always rethrown so callers are unaffected.

Notifier lifetime. The notifier is now created once per service generation and shared by the token and streaming clients rather than created per client. It dedupes per instance, so sharing is what keeps a blocked identity to a single notification regardless of which client observes it first. It is cleared in resetCodewhispererService, so signing out and back in with another blocked identity notifies again instead of being suppressed.

Scope.StreamingClientServiceToken only. The IAM variant serves a different surface, and the gate only denies Builder ID, which is bearer-token only.

Tests. Added one asserting the streaming client receives an observer — previously undefined, which is the gap. Reference-equality against the token client is not assertable in that harness because the token service is a sinon stub, so the test asserts only the streaming side.

Pre-existing failures on this branch, unchanged by either commit (verified by stashing and re-running): utils.test.ts 11 failing / 89 passing, and amazonQServer.test.ts 1 failing. Both identical before and after.

@ashishrp-aws
ashishrp-aws merged commit 13dd9d0 into Amazon-Q-Developer:feature/qdev-signup-messageAug 12, 2026
5 checks passed
ashishrp-aws added a commit that referenced this pull request Aug 12, 2026
#2799)
The serverInfo added in #2797 used one hardcoded name, but
AmazonQServiceServerFactory is instantiated twice -- AmazonQServiceServerIAM and
AmazonQServiceServerToken -- and runtimes including agent-standalone register both. Two
servers reporting the same name makes lspRouter reject initialize outright:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
That fails the whole language server, not just the duplicate. Observed in VS Code as:
Failed to start downloaded LSP, falling back to bundled LSP:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
The client then silently ran its bundled server instead, so Q appeared to work while
none of the access-blocked reporting existed, with only a client-side warning to show
for it.
serverName is now a required parameter rather than a shared constant, since a default
is precisely what let two instantiations collide. The two names are exported so the
uniqueness is assertable, and they must stay stable: the name is encoded into the id of
every notification the client echoes back.
Added a regression test on the distinctness. Verified it bites -- reintroducing the
collision gives 7 passing/2 failing, the fix gives 8 passing/1 failing. No existing
test registers two servers from one runtime, which is why this reached a release.
Pre-existing failure on this branch, unchanged: amazonQServer.test.ts
"hooks onUpdateConfiguration handler to LSP server".
ashishrp-aws added a commit that referenced this pull request Aug 13, 2026
… client (#2800)
* fix(amazonq): pass notification feature to the Q service manager (#2796)
The access-blocked notification added in #2794 never reached the client.
AmazonQServiceServerFactory destructures the features it forwards to the service
manager, and notification was not among them, so features.notification was always
undefined, the guard in serviceFactory never passed, onAccessBlocked was never
assigned, and the notifier could not run.
notification is optional on QServiceManagerFeatures so that existing constructions
and test fixtures keep compiling. That is also why omitting it here did not fail the
build -- it silently disabled client-facing reporting instead. Noted at the call site
so the next person adding a feature there does not repeat it.
Also set a stable id on the notification. Clients need to recognise it without
inspecting its text: the message is the service's own copy and is expected to change,
and FEATURE_NOT_SUPPORTED is reused across several RTS gates so the reason alone does
not identify this one. Both IDE clients already prefer the id when present and fall
back to matching the title only because the released server does not send one yet.
Verified: tsc clean, prettier clean, 6/6 notifier tests pass, and a server bundle
built from this branch contains the wiring where a bundle from the previous head did
not.
* fix(amazonq): declare serverInfo so notifications reach the client (#2797)
* fix(amazonq): declare serverInfo so notifications reach the client
The access-blocked notification still never reached the client after #2796. The
runtime only constructs a notification router for servers that declare serverInfo:
if (initializeResult?.serverInfo) {
this.notificationRouter = new RouterByServerName(initializeResult.serverInfo.name, ...)
}
AmazonQServiceServer returned only capabilities and awsServerCapabilities, so the
router was never built and notification.showNotification() logged "Notifications are
not supported: serverInfo is not defined" and dropped the notification. Observed in
VS Code: the block was detected and logged, then silently discarded.
This is the last piece. With #2794 (detect), #2796 (wire) and this change (deliver),
a blocked identity produces a notification the client can act on.
Added a regression test, because the failure mode is silent: nothing throws and only
a debug line marks the loss. The test asserts the exact name, which is deliberate --
the name is encoded into the id of every notification the client echoes back, so
renaming it strands followups for notifications already on screen.
Note: amazonQServer.test.ts has one pre-existing failure on this branch,
"hooks onUpdateConfiguration handler to LSP server", present before this change
(6 passing/1 failing before, 7 passing/1 failing after). Left alone as unrelated.
* fix(amazonq): observe access-blocked on the streaming client too
The observer added in #2794 was only on the token client. Chat runs through the
streaming client, so the one surface where a blocked identity actually shows up to the
user was the one place nothing was watching. Detection happened to work anyway because
the gate denies every operation and the A/B config fetch goes through the token client
moments after credentials arrive -- but that is incidental, not a guarantee.
Mirrors the token client exactly: middleware on the outermost initialize step so it
fires once per operation after retries are exhausted, the observer is called inside its
own try/catch, and the error is always rethrown so callers behave as before.
The notifier is now created once per service generation and shared by both clients
rather than created per client. The notifier dedupes per instance, so sharing is what
keeps a blocked identity to a single notification no matter which client sees it first.
It is cleared by resetCodewhispererService, so signing out and back in with another
blocked identity notifies again instead of being suppressed.
Scoped to StreamingClientServiceToken. The IAM variant serves a different surface and
the gate only denies Builder ID, which is bearer-token only.
Pre-existing failures on this branch, unchanged by this commit: utils.test.ts 11
failing (89 passing) and amazonQServer.test.ts 1 failing, both identical before and
after.
* fix(amazonq): give the IAM and token servers distinct serverInfo names (#2799)
The serverInfo added in #2797 used one hardcoded name, but
AmazonQServiceServerFactory is instantiated twice -- AmazonQServiceServerIAM and
AmazonQServiceServerToken -- and runtimes including agent-standalone register both. Two
servers reporting the same name makes lspRouter reject initialize outright:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
That fails the whole language server, not just the duplicate. Observed in VS Code as:
Failed to start downloaded LSP, falling back to bundled LSP:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
The client then silently ran its bundled server instead, so Q appeared to work while
none of the access-blocked reporting existed, with only a client-side warning to show
for it.
serverName is now a required parameter rather than a shared constant, since a default
is precisely what let two instantiations collide. The two names are exported so the
uniqueness is assertable, and they must stay stable: the name is encoded into the id of
every notification the client echoes back.
Added a regression test on the distinctness. Verified it bites -- reintroducing the
collision gives 7 passing/2 failing, the fix gives 8 passing/1 failing. No existing
test registers two servers from one runtime, which is why this reached a release.
Pre-existing failure on this branch, unchanged: amazonQServer.test.ts
"hooks onUpdateConfiguration handler to LSP server".
* test(amazonq): address review findings on the access-blocked observer (#2801)
Three review follow-ups, no behaviour change for users.
Name the streaming client's middleware, matching the token client. Without a name a
second registration stacks another observer rather than replacing the first, which would
report the same block twice, and the middleware is anonymous in SDK stack introspection.
Assert the server-name uniqueness against the real exported servers rather than the two
constants. Comparing constants cannot catch the same name being passed to both factory
calls, which is the mistake that actually shipped. Verified the test bites: making the
names identical fails it (7 passing/2 failing vs 8/1).
Add two tests for the streaming observer. They assert the wiring rather than the callback
because the existing harness stubs CodeWhispererStreaming.prototype.sendMessage, which
bypasses the middleware stack entirely -- a behavioural test there would pass even if the
middleware did not exist.
shared group: 337 passing / 45 failing, against 334 / 45 before, so the 3 new tests and
no new failures.
Sign up for freeto 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.

4 participants

@ashishrp-aws@codecov-commenter@chungjac@hezelin-work
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); fix(amazonq): declare serverInfo so notifications reach the client by ashishrp-aws · Pull Request #2797 · Amazon-Q-Developer/language-servers · GitHub
Skip to content

fix(amazonq): declare serverInfo so notifications reach the client - #2797

Merged
ashishrp-aws merged 2 commits into
Amazon-Q-Developer:feature/qdev-signup-messagefrom
ashishrp-aws:fix/qdev-signup-serverinfo
Aug 12, 2026
Merged

fix(amazonq): declare serverInfo so notifications reach the client#2797
ashishrp-aws merged 2 commits into
Amazon-Q-Developer:feature/qdev-signup-messagefrom
ashishrp-aws:fix/qdev-signup-serverinfo

Conversation

@ashishrp-aws

Copy link
Copy Markdown
Collaborator

Targets feature/qdev-signup-message, same as #2794 and #2796.

Problem

After #2796 the notification still never reached the client. The runtime only constructs a notification router for servers that declare serverInfo:

if(initializeResult?.serverInfo){this.notificationRouter=newRouterByServerName(initializeResult.serverInfo.name,this.encoding)}

AmazonQServiceServer's initializer returned only capabilities and awsServerCapabilities, so no router was built and showNotification took this branch:

if(!this.notificationRouter){this.logger.log(`Notifications are not supported: serverInfo is not defined`)}this.notificationRouter?.send(...)// no-op

Observed in VS Code against prod RTS with a blocked Builder ID — the block was detected and logged, then silently discarded:

[Warn] lserver: Q Developer plugin access is blocked for this identity: Please visit https://kiro.dev/ ...
lserver: Notifications are not supported: serverInfo is not defined

Two servers in this repo already do this correctly (aws-lsp-identity, aws-lsp-notification); this follows their convention.

Why it took three PRs

Each layer failed silently and independently:

PRLayerFailure mode
#2794detect the denialworked
#2796pass notification to the service manageroptional feature, so omitting it compiled and disabled reporting
thisdeclare serverInfono router, so showNotification is a no-op

Nothing threw at any layer. Worth considering whether showNotification should warn rather than log when it drops a notification — a server author has no way to notice today. Happy to raise that against the runtime separately.

Testing

  • Regression test added asserting the initializer returns serverInfo. The failure mode is silent, so without a test this regresses invisibly.
  • The name is asserted exactly, deliberately: RouterByServerName encodes it into the id of every notification the client echoes back, so renaming it strands followups for notifications already on screen.
  • tsc --noEmit clean, prettier clean, eslint clean (0 errors).
  • Verified end-to-end in VS Code with a server bundle built from this branch installed into the resolved flare directory.

Pre-existing failure, not from this change:amazonQServer.test.ts → "hooks onUpdateConfiguration handler to LSP server" fails on this branch before my change (6 passing/1 failing before, 7/1 after). Left alone as unrelated, but it should be looked at.

Correction to #2796

I claimed there that clients could match on id === 'qDevPluginAccessBlocked'. That is wrong. RouterByServerName.send() replaces the id with base64 of {"serverName":...,"id":...} before it reaches the client, so the raw string never arrives. id is the runtime's followup-routing token, not a semantic identifier. The id is still worth keeping (it is what makes followups routable), but clients cannot match on it as a plain string — both IDE clients currently identify the notification by title, and I'll follow up on a durable way to identify it.

The access-blocked notification still never reached the client after Amazon-Q-Developer#2796. The
runtime only constructs a notification router for servers that declare serverInfo:
if (initializeResult?.serverInfo) {
this.notificationRouter = new RouterByServerName(initializeResult.serverInfo.name, ...)
}
AmazonQServiceServer returned only capabilities and awsServerCapabilities, so the
router was never built and notification.showNotification() logged "Notifications are
not supported: serverInfo is not defined" and dropped the notification. Observed in
VS Code: the block was detected and logged, then silently discarded.
This is the last piece. With Amazon-Q-Developer#2794 (detect), Amazon-Q-Developer#2796 (wire) and this change (deliver),
a blocked identity produces a notification the client can act on.
Added a regression test, because the failure mode is silent: nothing throws and only
a debug line marks the loss. The test asserts the exact name, which is deliberate --
the name is encoded into the id of every notification the client echoes back, so
renaming it strands followups for notifications already on screen.
Note: amazonQServer.test.ts has one pre-existing failure on this branch,
"hooks onUpdateConfiguration handler to LSP server", present before this change
(6 passing/1 failing before, 7 passing/1 failing after). Left alone as unrelated.
@codecov-commenter

codecov-commenter commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.76812% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 62.30%. Comparing base (b0018f8) to head (c55b330).

Files with missing linesPatch %Lines
...codewhisperer/src/shared/streamingClientService.ts58.82%14 Missing ⚠️
.../aws-lsp-codewhisperer/src/shared/amazonQServer.ts0.00%8 Missing ⚠️
...mazonQServiceManager/AmazonQTokenServiceManager.ts88.88%3 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## feature/qdev-signup-message #2797 +/- ##
===============================================================
+ Coverage 60.47% 62.30% +1.83% 
===============================================================
Files 282 282 Lines 71465 71530 +65 Branches 4608 4827 +219 ===============================================================
+ Hits 43218 44570 +1352 + Misses 28158 26868 -1290 - Partials 89 92 +3 
FlagCoverage Δ
unittests62.30% <63.76%> (+1.83%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The observer added in Amazon-Q-Developer#2794 was only on the token client. Chat runs through the
streaming client, so the one surface where a blocked identity actually shows up to the
user was the one place nothing was watching. Detection happened to work anyway because
the gate denies every operation and the A/B config fetch goes through the token client
moments after credentials arrive -- but that is incidental, not a guarantee.
Mirrors the token client exactly: middleware on the outermost initialize step so it
fires once per operation after retries are exhausted, the observer is called inside its
own try/catch, and the error is always rethrown so callers behave as before.
The notifier is now created once per service generation and shared by both clients
rather than created per client. The notifier dedupes per instance, so sharing is what
keeps a blocked identity to a single notification no matter which client sees it first.
It is cleared by resetCodewhispererService, so signing out and back in with another
blocked identity notifies again instead of being suppressed.
Scoped to StreamingClientServiceToken. The IAM variant serves a different surface and
the gate only denies Builder ID, which is bearer-token only.
Pre-existing failures on this branch, unchanged by this commit: utils.test.ts 11
failing (89 passing) and amazonQServer.test.ts 1 failing, both identical before and
after.
@ashishrp-aws

Copy link
Copy Markdown
CollaboratorAuthor

Added a second commit: fix(amazonq): observe access-blocked on the streaming client too.

Why. The observer from #2794 was only on the token client. Chat runs through the streaming client, so the one surface where a blocked identity actually shows up to the user was the one place nothing was watching. Detection worked anyway only because the gate denies every operation and the A/B config fetch goes through the token client moments after credentials arrive — incidental, not a guarantee.

What. Mirrors the token client exactly: middleware on the outermost initialize step (fires once per operation after retries, not per attempt), observer called inside its own try/catch, error always rethrown so callers are unaffected.

Notifier lifetime. The notifier is now created once per service generation and shared by the token and streaming clients rather than created per client. It dedupes per instance, so sharing is what keeps a blocked identity to a single notification regardless of which client observes it first. It is cleared in resetCodewhispererService, so signing out and back in with another blocked identity notifies again instead of being suppressed.

Scope.StreamingClientServiceToken only. The IAM variant serves a different surface, and the gate only denies Builder ID, which is bearer-token only.

Tests. Added one asserting the streaming client receives an observer — previously undefined, which is the gap. Reference-equality against the token client is not assertable in that harness because the token service is a sinon stub, so the test asserts only the streaming side.

Pre-existing failures on this branch, unchanged by either commit (verified by stashing and re-running): utils.test.ts 11 failing / 89 passing, and amazonQServer.test.ts 1 failing. Both identical before and after.

@ashishrp-aws
ashishrp-aws merged commit 13dd9d0 into Amazon-Q-Developer:feature/qdev-signup-messageAug 12, 2026
5 checks passed
ashishrp-aws added a commit that referenced this pull request Aug 12, 2026
#2799)
The serverInfo added in #2797 used one hardcoded name, but
AmazonQServiceServerFactory is instantiated twice -- AmazonQServiceServerIAM and
AmazonQServiceServerToken -- and runtimes including agent-standalone register both. Two
servers reporting the same name makes lspRouter reject initialize outright:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
That fails the whole language server, not just the duplicate. Observed in VS Code as:
Failed to start downloaded LSP, falling back to bundled LSP:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
The client then silently ran its bundled server instead, so Q appeared to work while
none of the access-blocked reporting existed, with only a client-side warning to show
for it.
serverName is now a required parameter rather than a shared constant, since a default
is precisely what let two instantiations collide. The two names are exported so the
uniqueness is assertable, and they must stay stable: the name is encoded into the id of
every notification the client echoes back.
Added a regression test on the distinctness. Verified it bites -- reintroducing the
collision gives 7 passing/2 failing, the fix gives 8 passing/1 failing. No existing
test registers two servers from one runtime, which is why this reached a release.
Pre-existing failure on this branch, unchanged: amazonQServer.test.ts
"hooks onUpdateConfiguration handler to LSP server".
ashishrp-aws added a commit that referenced this pull request Aug 13, 2026
… client (#2800)
* fix(amazonq): pass notification feature to the Q service manager (#2796)
The access-blocked notification added in #2794 never reached the client.
AmazonQServiceServerFactory destructures the features it forwards to the service
manager, and notification was not among them, so features.notification was always
undefined, the guard in serviceFactory never passed, onAccessBlocked was never
assigned, and the notifier could not run.
notification is optional on QServiceManagerFeatures so that existing constructions
and test fixtures keep compiling. That is also why omitting it here did not fail the
build -- it silently disabled client-facing reporting instead. Noted at the call site
so the next person adding a feature there does not repeat it.
Also set a stable id on the notification. Clients need to recognise it without
inspecting its text: the message is the service's own copy and is expected to change,
and FEATURE_NOT_SUPPORTED is reused across several RTS gates so the reason alone does
not identify this one. Both IDE clients already prefer the id when present and fall
back to matching the title only because the released server does not send one yet.
Verified: tsc clean, prettier clean, 6/6 notifier tests pass, and a server bundle
built from this branch contains the wiring where a bundle from the previous head did
not.
* fix(amazonq): declare serverInfo so notifications reach the client (#2797)
* fix(amazonq): declare serverInfo so notifications reach the client
The access-blocked notification still never reached the client after #2796. The
runtime only constructs a notification router for servers that declare serverInfo:
if (initializeResult?.serverInfo) {
this.notificationRouter = new RouterByServerName(initializeResult.serverInfo.name, ...)
}
AmazonQServiceServer returned only capabilities and awsServerCapabilities, so the
router was never built and notification.showNotification() logged "Notifications are
not supported: serverInfo is not defined" and dropped the notification. Observed in
VS Code: the block was detected and logged, then silently discarded.
This is the last piece. With #2794 (detect), #2796 (wire) and this change (deliver),
a blocked identity produces a notification the client can act on.
Added a regression test, because the failure mode is silent: nothing throws and only
a debug line marks the loss. The test asserts the exact name, which is deliberate --
the name is encoded into the id of every notification the client echoes back, so
renaming it strands followups for notifications already on screen.
Note: amazonQServer.test.ts has one pre-existing failure on this branch,
"hooks onUpdateConfiguration handler to LSP server", present before this change
(6 passing/1 failing before, 7 passing/1 failing after). Left alone as unrelated.
* fix(amazonq): observe access-blocked on the streaming client too
The observer added in #2794 was only on the token client. Chat runs through the
streaming client, so the one surface where a blocked identity actually shows up to the
user was the one place nothing was watching. Detection happened to work anyway because
the gate denies every operation and the A/B config fetch goes through the token client
moments after credentials arrive -- but that is incidental, not a guarantee.
Mirrors the token client exactly: middleware on the outermost initialize step so it
fires once per operation after retries are exhausted, the observer is called inside its
own try/catch, and the error is always rethrown so callers behave as before.
The notifier is now created once per service generation and shared by both clients
rather than created per client. The notifier dedupes per instance, so sharing is what
keeps a blocked identity to a single notification no matter which client sees it first.
It is cleared by resetCodewhispererService, so signing out and back in with another
blocked identity notifies again instead of being suppressed.
Scoped to StreamingClientServiceToken. The IAM variant serves a different surface and
the gate only denies Builder ID, which is bearer-token only.
Pre-existing failures on this branch, unchanged by this commit: utils.test.ts 11
failing (89 passing) and amazonQServer.test.ts 1 failing, both identical before and
after.
* fix(amazonq): give the IAM and token servers distinct serverInfo names (#2799)
The serverInfo added in #2797 used one hardcoded name, but
AmazonQServiceServerFactory is instantiated twice -- AmazonQServiceServerIAM and
AmazonQServiceServerToken -- and runtimes including agent-standalone register both. Two
servers reporting the same name makes lspRouter reject initialize outright:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
That fails the whole language server, not just the duplicate. Observed in VS Code as:
Failed to start downloaded LSP, falling back to bundled LSP:
Duplicate servers defined: AWS Language Server for Amazon Q Developer
The client then silently ran its bundled server instead, so Q appeared to work while
none of the access-blocked reporting existed, with only a client-side warning to show
for it.
serverName is now a required parameter rather than a shared constant, since a default
is precisely what let two instantiations collide. The two names are exported so the
uniqueness is assertable, and they must stay stable: the name is encoded into the id of
every notification the client echoes back.
Added a regression test on the distinctness. Verified it bites -- reintroducing the
collision gives 7 passing/2 failing, the fix gives 8 passing/1 failing. No existing
test registers two servers from one runtime, which is why this reached a release.
Pre-existing failure on this branch, unchanged: amazonQServer.test.ts
"hooks onUpdateConfiguration handler to LSP server".
* test(amazonq): address review findings on the access-blocked observer (#2801)
Three review follow-ups, no behaviour change for users.
Name the streaming client's middleware, matching the token client. Without a name a
second registration stacks another observer rather than replacing the first, which would
report the same block twice, and the middleware is anonymous in SDK stack introspection.
Assert the server-name uniqueness against the real exported servers rather than the two
constants. Comparing constants cannot catch the same name being passed to both factory
calls, which is the mistake that actually shipped. Verified the test bites: making the
names identical fails it (7 passing/2 failing vs 8/1).
Add two tests for the streaming observer. They assert the wiring rather than the callback
because the existing harness stubs CodeWhispererStreaming.prototype.sendMessage, which
bypasses the middleware stack entirely -- a behavioural test there would pass even if the
middleware did not exist.
shared group: 337 passing / 45 failing, against 334 / 45 before, so the 3 new tests and
no new failures.
Sign up for freeto 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.

4 participants

@ashishrp-aws@codecov-commenter@chungjac@hezelin-work