fix: address PR #4 review suggestions

- Fix race condition in start() using Promise instead of boolean flag
- Document that tunnel intentionally doesn't limit response sizes (streaming)
- Improve token upload failure visibility with actionable user message

Built [OnSteroids](https://onsteroids.ai)

Co-Authored-By: OnSteroids <built@onsteroids.ai>
This commit is contained in:
Sergey Galuza
2026-01-14 18:06:58 +01:00
co-authored by OnSteroids
parent e7e95e6970
commit c3bfa34703
2 changed files with 34 additions and 13 deletions
+18 -6
View File
@@ -260,12 +260,24 @@ function uploadTokenToRemoteAsync(tokenPath: string, verbose: boolean): void {
import('../remote-token-uploader')
.then(({ uploadTokenToRemote, isRemoteUploadEnabled }) => {
if (isRemoteUploadEnabled()) {
// uploadTokenToRemote handles its own logging for success/failure
uploadTokenToRemote(tokenPath, verbose).catch((err: unknown) => {
// Unexpected error (not handled by uploadTokenToRemote)
const message = err instanceof Error ? err.message : String(err);
console.error(`[token-manager] Unexpected upload error: ${message}`);
});
// uploadTokenToRemote handles its own success/failure logging
// On failure, show additional warning so users know local token is still valid
uploadTokenToRemote(tokenPath, verbose)
.then((success) => {
if (!success) {
console.error(
'\n[!] Remote upload failed - token saved locally only. Run "ccs tokens upload" to retry.'
);
}
})
.catch((err: unknown) => {
// Unexpected error (not handled by uploadTokenToRemote)
const message = err instanceof Error ? err.message : String(err);
console.error(`[token-manager] Unexpected upload error: ${message}`);
console.error(
'[!] Token saved locally. Run "ccs tokens upload" to sync to remote server.'
);
});
}
})
.catch((err: unknown) => {
+16 -7
View File
@@ -7,6 +7,10 @@
*
* Flow:
* Claude CLI --HTTP--> Local Tunnel (port X) --HTTPS--> Remote CLIProxyAPI
*
* Note: Unlike CodexReasoningProxy, this tunnel does NOT buffer or limit response sizes.
* Responses are streamed directly (pipe) which is appropriate for a transparent tunnel.
* Socket-level timeouts handle hung connections; size limits are enforced by the remote server.
*/
import * as http from 'http';
@@ -31,7 +35,7 @@ export interface HttpsTunnelConfig {
export class HttpsTunnelProxy {
private server: http.Server | null = null;
private port: number | null = null;
private starting = false;
private startingPromise: Promise<number> | null = null;
private activeConnections = new Set<Socket>();
private readonly config: Required<
Pick<
@@ -75,11 +79,13 @@ export class HttpsTunnelProxy {
}
async start(): Promise<number> {
// Prevent race condition with concurrent start() calls
if (this.server || this.starting) return this.port ?? 0;
this.starting = true;
// Already started
if (this.server) return this.port ?? 0;
return new Promise((resolve, reject) => {
// Prevent race condition: if start() is already in progress, return the same promise
if (this.startingPromise) return this.startingPromise;
this.startingPromise = new Promise((resolve, reject) => {
this.server = http.createServer((req, res) => {
void this.handleRequest(req, res);
});
@@ -93,8 +99,8 @@ export class HttpsTunnelProxy {
this.server.listen(0, '127.0.0.1', () => {
const address = this.server?.address();
this.port = typeof address === 'object' && address ? address.port : 0;
this.starting = false;
if (this.port === 0) {
this.startingPromise = null;
reject(new Error('Failed to bind to any port'));
return;
}
@@ -105,10 +111,12 @@ export class HttpsTunnelProxy {
});
this.server.on('error', (err) => {
this.starting = false;
this.startingPromise = null;
reject(err);
});
});
return this.startingPromise;
}
stop(): void {
@@ -123,6 +131,7 @@ export class HttpsTunnelProxy {
this.server.close();
this.server = null;
this.port = null;
this.startingPromise = null;
this.log('Stopped');
}