You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(server): log canceled relay requests at debug level - #32215
Cloudflared 2026.10.4 reports a downstream HTTP request cancellation with Incoming request ended abruptly: context canceled. The relay classifier currently turns that routine disconnect into Relay client reported a transport warning, contributing to the noise reported in #17765.
Change
Classify the two known cloudflared ERR cancellation formats as debug. Deadline failures, other tunnel cancellation errors, and fatal/panic output retain warning severity. Extend the existing classifier test with those cases.
Scope and approval
Addresses #17765's cancellation-log defect only. The snapshot timeout and request-concurrency concerns remain open. This is a small, reproducible correction to an existing log classifier under CONTRIBUTING.md's focused bug-fix exception; it introduces no configuration or client behavior.
Verification
Windows, Node 26.12.1, pnpm 12.14.0; frozen-lockfile install in the isolated checkout. Commands below use that checkout's local node_modules/.bin/vp.ps1.
Baseline from apps/server: vp test run src/cloud/ManagedEndpointRuntime.test.ts -t "classifies Cloudflare connection and warning output" failed with expected 'warning' to be 'debug' (1 failed, 22 skipped).
Fixed from apps/server: vp test run src/cloud/ManagedEndpointRuntime.test.ts src/provider/AgentContextBudget.test.ts passed all 25 tests across 2 files.
Runtime commands: node issue-17765-runtime.mjs <checkout> baseline before the production edit, then node issue-17765-runtime.mjs <checkout> fixed. The external fixture and harness are included below for reproduction.
Runtime verification used the actual ManagedEndpointRuntime layer, Node child-process spawner, secret store, HTTP readiness client, output observer, and rotating relay log in isolated temporary state. A deterministic connector process opened a loopback HTTP server; aborting an in-flight request caused it to emit cloudflared's two cancellation frames, followed by four error controls. This exercised the production process/network/logging path without a live Cloudflare account or client UI.
No live tunnel, browser, desktop, or mobile pass is claimed; the changed behavior is server logging only. No paid provider turns or live userdata were used.
Runtime reproduction fixture (save both files together outside the checkout)
issue-17765-connector-fixture.mjs
import{createServer}from'node:http';import{once}from'node:events';// A real loopback HTTP cancellation drives the recorded cloudflared protocol lines.letmarkArrived;constarrived=newPromise((resolve)=>{markArrived=resolve;});letmarkCancelled;constcancelled=newPromise((resolve)=>{markCancelled=resolve;});constline=(text)=>process.stderr.write(`2026-11-07T00:00:00Z ${text}\n`);constserver=createServer(async(request,response)=>{if(request.url==='/ready'){response.setHeader('content-type','application/json');response.end(JSON.stringify({status: 200,readyConnections: 1}));}elseif(request.url==='/hold'){request.once('close',()=>{line('ERR error="Incoming request ended abruptly: context canceled" connIndex=0');line('ERR Request failed error="Incoming request ended abruptly: context canceled" connIndex=0');markCancelled();});markArrived();}elseif(request.url==='/arrived'){awaitarrived;response.end('request arrived');}elseif(request.url==='/controls'){awaitcancelled;line('ERR error="Incoming request ended abruptly: context deadline exceeded" connIndex=0');line('ERR Failed to serve tunnel connection error="context canceled"');line('FTL error="Incoming request ended abruptly: context canceled"');line('PNC error="Incoming request ended abruptly: context canceled"');line('INF Registered tunnel connection connIndex=1 protocol=quic');response.end('control lines emitted');}else{response.writeHead(404).end();}});server.listen(0,'127.0.0.1');awaitonce(server,'listening');line(`INF Starting metrics server on 127.0.0.1:${server.address().port}/metrics`);line('INF Registered tunnel connection connIndex=0 protocol=quic');// This module is preloaded by the production process spawner; do not load its CLI placeholder.awaitnewPromise(()=>{});
issue-17765-runtime.mjs
importassertfrom'node:assert/strict';import{existsSync,mkdtempSync,readFileSync,writeFileSync}from'node:fs';import{createRequire}from'node:module';import{dirname,join}from'node:path';import{fileURLToPath,pathToFileURL}from'node:url';constcheckout=process.argv[2];constmode=process.argv[3];assert.ok(checkout&&['baseline','fixed'].includes(mode));assert.ok(existsSync(join(checkout,'apps/server/node_modules/effect/package.json')),'Local server dependencies are not linked; ancestor fallback is forbidden.');constrequireLocal=createRequire(join(checkout,'apps/server/package.json'));constpkg=(name)=>import(pathToFileURL(requireLocal.resolve(name)).href);constsource=(path)=>import(pathToFileURL(join(checkout,path)).href);const[Effect,Layer,Logger,Deferred,Exit,References,ConfigProvider,NodeServices,NodeCrypto,FetchHttpClient,HostProcess,RelayClient,ServerConfig,ServerSecretStore,ManagedPolicy,ManagedEndpointRuntime]=awaitPromise.all([pkg('effect/Effect'),pkg('effect/Layer'),pkg('effect/Logger'),pkg('effect/Deferred'),pkg('effect/Exit'),pkg('effect/References'),pkg('effect/ConfigProvider'),pkg('@effect/platform-node/NodeServices'),pkg('@effect/platform-node/NodeCrypto'),pkg('effect/http/FetchHttpClient'),source('packages/shared/src/HostProcess.ts'),source('packages/shared/src/relayClient.ts'),source('apps/server/src/config.ts'),source('apps/server/src/auth/ServerSecretStore.ts'),source('apps/server/src/policy/ManagedPolicy.ts'),source('apps/server/src/cloud/ManagedEndpointRuntime.ts'),]);constscratch=dirname(fileURLToPath(import.meta.url));conststate=mkdtempSync(join(scratch,`issue-17765-${mode}-`));constlogs=[];constaddress=Deferred.makeUnsafe();constcontrolsSeen=Deferred.makeUnsafe();constcapturedPids=newSet();constlogger=Logger.make(({ logLevel, message })=>{if(!Array.isArray(message)||!String(message[0]).startsWith('Relay client'))return;constattributes=message[1]??{};logs.push({level: logLevel,message: message[0],output: attributes.output});if(typeofattributes.pid==='number')capturedPids.add(attributes.pid);constoutput=attributes.output??'';constmatch=/Startingmetricsserveron(127\.0\.0\.1:\d+)\/metrics/.exec(output);if(match)Deferred.doneUnsafe(address,Exit.succeed(`http://${match[1]}`));if(output.includes('Registered tunnel connection connIndex=1'))Deferred.doneUnsafe(controlsSeen,Exit.void);});constfixtureUrl=newURL('./issue-17765-connector-fixture.mjs',import.meta.url).href;constenv={ ...process.env,NODE_OPTIONS: `--import=${fixtureUrl}`};deleteenv.VITE_HTTP_URL;deleteenv.VITE_WS_URL;constconfiguration=ServerConfig.layerTest(checkout,state).pipe(Layer.provide(NodeServices.layer));constsystem=Layer.mergeAll(NodeServices.layer,NodeCrypto.layer,configuration);constdependencies=Layer.mergeAll(ManagedPolicy.layerTest(),ServerSecretStore.layer.pipe(Layer.provide(system)),FetchHttpClient.layer,configuration,Layer.succeed(RelayClient.RelayClient,RelayClient.RelayClient.of({resolve: Effect.succeed({status: 'available',executablePath: process.execPath,source: 'path',version: RelayClient.CLOUDFLARED_VERSION}),install: Effect.die('unexpected installer'),installWithProgress: ()=>Effect.die('unexpected installer'),pruneManagedVersions: Effect.void,})),).pipe(Layer.provideMerge(system));try{awaitEffect.runPromise(Effect.gen(function*(){constcontext=yield*Layer.build(ManagedEndpointRuntime.layer.pipe(Layer.provide(dependencies)));construntime=yield*Effect.service(ManagedEndpointRuntime.CloudManagedEndpointRuntime).pipe(Effect.provide(context));yield*runtime.applyConfig({providerKind: 'cloudflare_tunnel',connectorToken: 'fixture-only-no-cloud-access',tunnelId: 'fixture-17765'});constorigin=yield*Deferred.await(address);yield*Effect.promise(async()=>{constcontroller=newAbortController();constheld=fetch(`${origin}/hold`,{signal: controller.signal}).catch((error)=>error);assert.equal(await(awaitfetch(`${origin}/arrived`)).text(),'request arrived');controller.abort();assert.equal((awaitheld).name,'AbortError');assert.equal(await(awaitfetch(`${origin}/controls`)).text(),'control lines emitted');});yield*Deferred.await(controlsSeen);constoutput=logs.filter((entry)=>entry.output);constcancellations=output.filter((entry)=>/ERR.*error="Incomingrequestendedabruptly:contextcanceled"/.test(entry.output));assert.equal(cancellations.length,2);assert.deepEqual(cancellations.map((entry)=>entry.level),mode==='baseline' ? ['Warn','Warn'] : ['Debug','Debug']);constcontrols=output.filter((entry)=>/deadlineexceeded|Failedtoservetunnel|FTL|PNC/.test(entry.output));assert.equal(controls.length,4);assert.ok(controls.every((entry)=>entry.level==='Warn'));constfacts=yield*runtime.inspectConnector;assert.equal(facts.readyConnections,1);assert.equal(facts.protocol,'quic');constpersisted=readFileSync(join(state,'userdata/logs/relay-client.ndjson'),'utf8').trim().split('\n').map((line)=>JSON.parse(line));constpersistedCancellations=persisted.filter((entry)=>/ERR.*error="Incomingrequestendedabruptly:contextcanceled"/.test(entry.message[1]?.output??''));assert.deepEqual(persistedCancellations.map((entry)=>entry.level),mode==='baseline' ? ['WARN','WARN'] : ['DEBUG','DEBUG']);constpersistedControls=persisted.filter((entry)=>/deadlineexceeded|Failedtoservetunnel|FTL|PNC/.test(entry.message[1]?.output??''));assert.equal(persistedControls.length,4);assert.ok(persistedControls.every((entry)=>entry.level==='WARN'));yield*runtime.applyConfig(null);assert.equal(yield*runtime.inspectConnector,null);}).pipe(Effect.scoped,Effect.withLogger(logger),Effect.provideService(References.MinimumLogLevel,'Debug'),Effect.provideService(HostProcess.Environment,env),Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env }))),));console.log(JSON.stringify({ mode, state,capturedPids: [...capturedPids],observations: logs,result: 'passed',boundary: 'Real production connector service, OS child process, loopback HTTP abort, output observer and log sink; deterministic cloudflared protocol fixture, no live cloud tunnel or UI.'},null,2));}finally{writeFileSync(join(state,'transcript.json'),JSON.stringify({ mode,capturedPids: [...capturedPids], logs },null,2));}
The required vp run check:changed --concurrency 1 was attempted. It printed 2 files since 32a0531748c4 (origin/main), then stalled in the ignored-input git ls-files junction scan tracked by #32023. I stopped only that retained session and ran every selected equivalent stage serially, without changing check configuration or creating a check cache:
Format and lint: both changed files passed (vp fmt --check --no-error-on-unmatched-pattern <both files> and vp lint --no-error-on-unmatched-pattern <both files>).
Exports knip, with the unchanged knip:check:exports arguments below: passed (saved exit code 0).
vp run --filter ./apps/server generate --check: passed.
For each of t3, @t3tools/scripts, and @t3tools/evals: vp run --cache --parallel --concurrency-limit 1 --filter <package> typecheck: passed, exit 0. Earlier attempts encountered host memory-allocation failures; all three were rerun successfully with the same checker settings. Existing Effect suggestions were emitted, with no error diagnostics.
The two selected test files listed above passed from their owning package. No repo-wide test suite or model eval was run; this change contains no model-facing text.
The reason will be displayed to describe this comment to others. Learn more.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.
Macroscope's review found this PR approvable — Canceled relay-request messages move from warning to debug logging. The change is narrowly scoped and leaves tunnel recovery and other error reporting unchanged.
You can add or adjust custom eligibility rules. Learn more.
No actionable comments were generated in the recent review.
Walkthrough
The relay output classifier now treats a specific canceled-request error as debug. Tests cover both accepted prefixes and confirm warning classifications for other error messages.
classifyRelayClientOutput classifies the specified context-canceled error as debug, with or without the Request failed prefix. Tests also verify warning classifications for deadline, connection-serving, fatal, and panic output.
Issue 17765: Canceled Cloudflared requests were logged as warnings. This change classifies the specified canceled-request output as debug.
Suggested reviewers:juliusmarminge
Merge Risk: Minimal · up to b1cce
The change lowers log severity for routine relay cancellations while retaining warnings for the reported control cases. No merge-blocking risk is established.
Pre-merge checks: 4
Check name
Status
Explanation
Title check
The title clearly describes the main logging change.
Description check
The description covers the problem, change, scope, approval exception, and verification. It also states testing limits and identifies the model and harness.
Linked Issues check
Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check
Check skipped because no linked issues were found for this pull request.
Finishing Touches
Generate unit tests (beta)
Create a new PR
Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.
This branch has not been deployed
No deployments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
size:XS0-9 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.
2 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Cloudflared 2026.10.4 reports a downstream HTTP request cancellation with
Incoming request ended abruptly: context canceled. The relay classifier currently turns that routine disconnect intoRelay client reported a transport warning, contributing to the noise reported in #17765.Change
Classify the two known cloudflared ERR cancellation formats as debug. Deadline failures, other tunnel cancellation errors, and fatal/panic output retain warning severity. Extend the existing classifier test with those cases.
Scope and approval
Addresses #17765's cancellation-log defect only. The snapshot timeout and request-concurrency concerns remain open. This is a small, reproducible correction to an existing log classifier under CONTRIBUTING.md's focused bug-fix exception; it introduces no configuration or client behavior.
Verification
Windows, Node 26.12.1, pnpm 12.14.0; frozen-lockfile install in the isolated checkout. Commands below use that checkout's local
node_modules/.bin/vp.ps1.apps/server:vp test run src/cloud/ManagedEndpointRuntime.test.ts -t "classifies Cloudflare connection and warning output"failed withexpected 'warning' to be 'debug'(1 failed, 22 skipped).apps/server:vp test run src/cloud/ManagedEndpointRuntime.test.ts src/provider/AgentContextBudget.test.tspassed all 25 tests across 2 files.Runtime commands:
node issue-17765-runtime.mjs <checkout> baselinebefore the production edit, thennode issue-17765-runtime.mjs <checkout> fixed. The external fixture and harness are included below for reproduction.Runtime verification used the actual ManagedEndpointRuntime layer, Node child-process spawner, secret store, HTTP readiness client, output observer, and rotating relay log in isolated temporary state. A deterministic connector process opened a loopback HTTP server; aborting an in-flight request caused it to emit cloudflared's two cancellation frames, followed by four error controls. This exercised the production process/network/logging path without a live Cloudflare account or client UI.
No live tunnel, browser, desktop, or mobile pass is claimed; the changed behavior is server logging only. No paid provider turns or live userdata were used.
Runtime reproduction fixture (save both files together outside the checkout)
issue-17765-connector-fixture.mjs
issue-17765-runtime.mjs
The required
vp run check:changed --concurrency 1was attempted. It printed2 files since 32a0531748c4 (origin/main), then stalled in the ignored-inputgit ls-filesjunction scan tracked by #32023. I stopped only that retained session and ran every selected equivalent stage serially, without changing check configuration or creating a check cache:vp fmt --check --no-error-on-unmatched-pattern <both files>andvp lint --no-error-on-unmatched-pattern <both files>).vp exec knip --include files,dependencies --no-config-hints: passed.knip:check:exportsarguments below: passed (saved exit code 0).vp run --filter ./apps/server generate --check: passed.t3,@t3tools/scripts, and@t3tools/evals:vp run --cache --parallel --concurrency-limit 1 --filter <package> typecheck: passed, exit 0. Earlier attempts encountered host memory-allocation failures; all three were rerun successfully with the same checker settings. Existing Effect suggestions were emitted, with no error diagnostics.git diff --check: passed.Exact format, lint, and exports commands
Implemented with OpenAI GPT-6 via the Codex harness.