Summary
@google-cloud/common@8.0.2 introduced joinURIComponents() in Service.request_() as part of PR #9188.
While encoding URI path components is appropriate for user-supplied resource IDs, it also encodes the internal DEFAULT_PROJECT_ID_TOKEN ({{projectId}}).
That token is expected to remain unchanged until decorateRequest() calls replaceProjectIdToken() and substitutes the actual project ID.
As a result, clients relying on lazy project ID resolution / ADC — for example:
without an explicit projectId — may send the encoded literal:
to the backend instead of the resolved project ID.
Regression
@google-cloud/common@8.0.1
request_()
→ URI: ".../projects/{{projectId}}/queries"
decorateRequest()
→ replaceProjectIdToken(uri, "my-project")
→ placeholder is found and replaced
BigQuery receives:
".../projects/my-project/queries" ✓
@google-cloud/common@8.0.2
request_()
→ joinURIComponents()
→ "{{projectId}}" becomes "%7B%7BprojectId%7D%7D"
→ URI: ".../projects/%7B%7BprojectId%7D%7D/queries"
decorateRequest()
→ replaceProjectIdToken(uri, "my-project")
→ "{{projectId}}" is no longer present
BigQuery receives:
".../projects/%7B%7BprojectId%7D%7D/queries" ✗
Result: 404
Minimal reproduction
makeAuthenticatedRequest is assigned as an instance property, so it needs to be replaced after constructing the Service.
The following reproduction captures the URI passed to makeAuthenticatedRequest. This allows the behavior to be reproduced without depending on ADC credentials, network access, or a GCP environment.
const {Service} = require('@google-cloud/common');
const svc = new Service(
{
baseUrl: 'https://bigquery.googleapis.com/bigquery/v2',
scopes: ['https://www.googleapis.com/auth/bigquery'],
packageJson: {
name: '@google-cloud/bigquery',
version: '9.0.3',
},
},
{}, // no explicit projectId → DEFAULT_PROJECT_ID_TOKEN
);
let capturedUri;
svc.makeAuthenticatedRequest = reqOpts => {
capturedUri = reqOpts.uri;
};
svc.request_({uri: 'queries'}, () => {});
console.log(capturedUri);
Observed behavior:
@google-cloud/common@8.0.1
.../projects/{{projectId}}/queries
@google-cloud/common@8.0.2
.../projects/%7B%7BprojectId%7D%7D/queries
In 8.0.1, the placeholder remains available for replaceProjectIdToken().
In 8.0.2, it has already been encoded by the time request decoration occurs.
Connection to #9188
PR #9188 mentions that the BigQuery system tests remained skipped:
Unskip (describe.skip → describe) and run the BigQuery system tests once the downstream tests are active.
A system test covering BigQuery initialization through ADC without an explicit projectId would likely catch this regression.
Suggested regression tests
At the Service.request_() level
it('should preserve DEFAULT_PROJECT_ID_TOKEN in the URI', done => {
const service = new Service(
{
baseUrl: 'https://example.googleapis.com/v1',
scopes: [],
packageJson: {
name: 'test',
version: '1.0.0',
},
},
{}
);
service.makeAuthenticatedRequest = reqOpts => {
assert.match(reqOpts.uri, /projects\/\{\{projectId\}\}\//);
done();
};
service.request_({uri: 'queries'}, () => {});
});
At the joinURIComponents() level
assert.strictEqual(
joinURIComponents(
'https://example.com/projects',
'{{projectId}}',
'queries'
),
'https://example.com/projects/{{projectId}}/queries'
);
Affected scope
This potentially affects clients extending Service that:
- include
DEFAULT_PROJECT_ID_TOKEN in request paths, and
- rely on lazy / ADC project ID resolution.
Confirmed affected configuration:
@google-cloud/bigquery@9.0.3
@google-cloud/common@8.0.2
new BigQuery() without an explicit projectId
Downgrading @google-cloud/common to 8.0.1 restores the previous behavior.
Workaround
For pnpm:
{
"pnpm": {
"overrides": {
"@google-cloud/bigquery@9>@google-cloud/common": "8.0.1"
}
}
}
Possible fix
One option would be to preserve DEFAULT_PROJECT_ID_TOKEN while encoding URI components.
The token is an internal symbolic placeholder rather than a concrete resource ID, and still needs to be visible to replaceProjectIdToken() later in the request lifecycle.
Alternatively, replaceProjectIdToken() in @google-cloud/projectify could recognize both:
and:
However, that would make project substitution aware of URI-encoding behavior from another layer.
Preserving the placeholder before request decoration therefore seems preferable.
Environment
@google-cloud/common: 8.0.2 (affected)
@google-cloud/common: 8.0.1 (working)
@google-cloud/bigquery: 9.0.3
Node.js: >=22
Regression appears to have been introduced by #9188.
Related
Summary
@google-cloud/common@8.0.2introducedjoinURIComponents()inService.request_()as part of PR #9188.While encoding URI path components is appropriate for user-supplied resource IDs, it also encodes the internal
DEFAULT_PROJECT_ID_TOKEN({{projectId}}).That token is expected to remain unchanged until
decorateRequest()callsreplaceProjectIdToken()and substitutes the actual project ID.As a result, clients relying on lazy project ID resolution / ADC — for example:
without an explicit
projectId— may send the encoded literal:to the backend instead of the resolved project ID.
Regression
@google-cloud/common@8.0.1@google-cloud/common@8.0.2Minimal reproduction
makeAuthenticatedRequestis assigned as an instance property, so it needs to be replaced after constructing theService.The following reproduction captures the URI passed to
makeAuthenticatedRequest. This allows the behavior to be reproduced without depending on ADC credentials, network access, or a GCP environment.Observed behavior:
In
8.0.1, the placeholder remains available forreplaceProjectIdToken().In
8.0.2, it has already been encoded by the time request decoration occurs.Connection to #9188
PR #9188 mentions that the BigQuery system tests remained skipped:
A system test covering BigQuery initialization through ADC without an explicit
projectIdwould likely catch this regression.Suggested regression tests
At the
Service.request_()levelAt the
joinURIComponents()levelAffected scope
This potentially affects clients extending
Servicethat:DEFAULT_PROJECT_ID_TOKENin request paths, andConfirmed affected configuration:
Downgrading
@google-cloud/commonto8.0.1restores the previous behavior.Workaround
For pnpm:
{ "pnpm": { "overrides": { "@google-cloud/bigquery@9>@google-cloud/common": "8.0.1" } } }Possible fix
One option would be to preserve
DEFAULT_PROJECT_ID_TOKENwhile encoding URI components.The token is an internal symbolic placeholder rather than a concrete resource ID, and still needs to be visible to
replaceProjectIdToken()later in the request lifecycle.Alternatively,
replaceProjectIdToken()in@google-cloud/projectifycould recognize both:and:
However, that would make project substitution aware of URI-encoding behavior from another layer.
Preserving the placeholder before request decoration therefore seems preferable.
Environment
Regression appears to have been introduced by #9188.
Related
joinURIComponents()inService.request_()googleapis-common@8.0.2/google-auth-library@10.5.0dependency issue; appears unrelated to this regression