googleapis / googleapis/google-cloud-node
gax: stub-creation-time auth failure bypasses the API-call promise — application .catch() cannot intercept it (surfaces as uncaughtException)
- Dominant language
- TypeScript
- Stars
- 3.2k
- Forks
- 712
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 99
Description
## Environment
- `google-gax` 4.6.1 (resolved transitively via `@google-cloud/logging` 11.3.0)
- `google-auth-library` (as resolved by the above)
- Node.js v22.x, Linux/macOS
## Summary
When credential resolution fails at **stub creation time** (e.g. `GOOGLE_APPLICATION_CREDENTIALS` points at a missing file), the failure does **not** propagate through the API call's returned promise. An application's `.catch()` on the API call never fires — instead the error surfaces as a process-level `uncaughtException` (and with no handler installed, kills the process).
## Reproduction
```js
// GOOGLE_APPLICATION_CREDENTIALS=/tmp/definitely-does-not-exist-credentials.json node repro.js
const { Logging } = require('@google-cloud/logging'); // any gax-based client reproduces
process.on('unhandledRejection', (e) => console.log('[unhandledRejection]', e.message));
process.on('uncaughtException', (e) => {
console.log('[uncaughtException — should have been a promise rejection]', e.message);
process.exit(0);
});
const log = new Logging().log('repro-log');
const entry = log.entry({ resource: { type: 'global' } }, { message: 'hello' });
log
.write(entry)
.then(() => console.log('[then] ok'))
.catch((e) => console.log('[catch] caught as expected:', e.message)); // ← never runs
```
Observed output:
```
[uncaughtException — should have been a promise rejection] The file at /tmp/definitely-does-not-exist-credentials.json does not exist, or it is not a file. ENOENT: no such file or directory ...
```
Stack points into stub creation:
```
at GoogleAuth._getApplicationCredentialsFromFilePath (google-auth-library/build/src/auth/googleauth.js:375)
...
at GrpcClient.createStub (google-gax/build/src/grpc.js:318)
```
The explicit `.catch()` on the write promise never fires; `unhandledRejection` never fires either — the error bypasses the promise chain entirely. (Also reproduced in the client library's own wrapper: `@google-cloud/logging`'s `logging_service_v2_client.js` has a `throw err;` in the stub-initialization path that re-throws outside any user-reachable promise.)
## Expected
A credential/auth failure during lazy stub creation should reject the pending API-call promise(s), so application code can handle it (`.catch()`, retry, fallback) like any other API error.
## Impact
Any long-running service that treats logging/telemetry as non-critical and wraps every call in try/catch (or `.catch()`) still crashes on boot-time/lazy-init auth misconfiguration — there is no userland way to intercept it short of a process-wide `uncaughtException` handler. We currently work around it by issuing a deliberate "pre-warm" write at startup so the failure happens at a controlled fail-fast point.
Contributor guide
Research direction
Start with the reproduction and trace lazy stub creation through google-gax/build/src/grpc.js at GrpcClient.createStub, then inspect logging_service_v2_client.js for the stub-initialization throw. Verify that a credential failure from the missing-file setup reaches the pending write promise and is handled by its .catch(), rather than surfacing as uncaughtException.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- google-cloud, node.js, typescript
- Domain
- api, authentication
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100