@@ -13,6 +13,7 @@ import { loadConfig } from "./index.js";
1313import { resetPricingMetadataRefreshForTests } from "../cost/pricing-metadata.js" ;
1414import { setProviderContextWindowOverrides } from "../provider/context-window.js" ;
1515import { withMockedHomedir } from "../../testkit/mock-module.js" ;
16+ import { captureStderr } from "../../testkit/capture-stderr.js" ;
1617import { isSettings , loadSettings , saveGlobalSettings } from "./settings.js" ;
1718
1819test . each ( [
@@ -680,3 +681,110 @@ test("--config composes with OAuth profile auth instead of suppressing it", asyn
680681 await rm ( cwd , { recursive : true , force : true } ) ;
681682 }
682683} ) ;
684+
685+ // CL-10226 (Part 1): some external tools' provider blocks carry fields Corbits
686+ // does not honor. They pass ProviderSettingsSchema (which stays fail-open on
687+ // unknown keys) and are silently dropped. The settings load path must warn on
688+ // stderr while still loading successfully.
689+ test ( "settings load warns and still loads when a provider carries ignored fields" , async ( ) => {
690+ const cwd = await mkdtemp ( join ( tmpdir ( ) , "ic-unit-config-ignored-fields-" ) ) ;
691+ const path = join ( cwd , "settings.json" ) ;
692+ const stderr = captureStderr ( ) ;
693+ try {
694+ await writeFile (
695+ path ,
696+ JSON . stringify ( {
697+ providers : {
698+ external : {
699+ baseURL : "https://external.example/v1" ,
700+ keyless : true ,
701+ models : [ "test-model" ] ,
702+ supportsThinkingTokenBudget : true ,
703+ thinkingLevelMap : { low : 1 } ,
704+ samplingParams : { temperature : 0.5 } ,
705+ compat : { supportsReasoningEffort : true } ,
706+ } ,
707+ } ,
708+ } ) ,
709+ ) ;
710+ const loaded = await loadSettings ( path ) ;
711+ expect ( loaded ) . not . toBeNull ( ) ;
712+ expect ( loaded ?. providers . external ) . toBeDefined ( ) ;
713+ expect ( stderr . output ( ) ) . toContain (
714+ '"external" sets fields that are not honored by Corbits and will be ignored' ,
715+ ) ;
716+ expect ( stderr . output ( ) ) . toContain ( "supportsThinkingTokenBudget" ) ;
717+ expect ( stderr . output ( ) ) . toContain ( "thinkingLevelMap" ) ;
718+ expect ( stderr . output ( ) ) . toContain ( "samplingParams" ) ;
719+ expect ( stderr . output ( ) ) . toContain ( "compat → supportsReasoningEffort" ) ;
720+ expect ( stderr . output ( ) ) . toContain (
721+ "These fields are not yet supported; configure effort/thinking via provider/model-level settings." ,
722+ ) ;
723+ } finally {
724+ stderr . restore ( ) ;
725+ await rm ( cwd , { recursive : true , force : true } ) ;
726+ }
727+ } ) ;
728+
729+ test ( "settings load does not warn for a normal provider block" , async ( ) => {
730+ const cwd = await mkdtemp ( join ( tmpdir ( ) , "ic-unit-config-no-ignored-" ) ) ;
731+ const path = join ( cwd , "settings.json" ) ;
732+ const stderr = captureStderr ( ) ;
733+ try {
734+ await writeFile (
735+ path ,
736+ JSON . stringify ( {
737+ providers : {
738+ external : {
739+ baseURL : "https://external.example/v1" ,
740+ keyless : true ,
741+ models : [ "test-model" ] ,
742+ } ,
743+ } ,
744+ } ) ,
745+ ) ;
746+ const loaded = await loadSettings ( path ) ;
747+ expect ( loaded ) . not . toBeNull ( ) ;
748+ expect ( stderr . output ( ) ) . not . toContain ( "not honored by Corbits" ) ;
749+ expect ( stderr . output ( ) ) . not . toContain ( "supportsThinkingTokenBudget" ) ;
750+ } finally {
751+ stderr . restore ( ) ;
752+ await rm ( cwd , { recursive : true , force : true } ) ;
753+ }
754+ } ) ;
755+
756+ // The same warn must surface on the config path (--config / global settings),
757+ // which routes through loadSettings... → normalizeParsedSettings.
758+ test ( "loadConfig warns and still loads when a provider carries ignored fields" , async ( ) => {
759+ const cwd = await mkdtemp ( join ( tmpdir ( ) , "ic-unit-config-ignored-config-" ) ) ;
760+ const globalSettingsPath = join ( cwd , "global.json" ) ;
761+ const stderr = captureStderr ( ) ;
762+ try {
763+ await writeFile (
764+ globalSettingsPath ,
765+ JSON . stringify ( {
766+ defaultProvider : "external" ,
767+ providers : {
768+ external : {
769+ baseURL : "https://external.example/v1" ,
770+ apiKey : "test-key" ,
771+ models : [ "test-model" ] ,
772+ samplingParams : { temperature : 0.5 } ,
773+ } ,
774+ } ,
775+ } ) ,
776+ ) ;
777+ const { impl } = offlineFetch ( ) ;
778+ const config = await loadConfig ( [ "--cwd" , cwd , "do something" ] , {
779+ globalSettingsPath,
780+ pricing : { fetchImpl : impl } ,
781+ } ) ;
782+ expect ( config . configured ) . toBe ( true ) ;
783+ if ( config . configured ) expect ( config . providerName ) . toBe ( "external" ) ;
784+ expect ( stderr . output ( ) ) . toContain ( "not honored by Corbits" ) ;
785+ expect ( stderr . output ( ) ) . toContain ( "samplingParams" ) ;
786+ } finally {
787+ stderr . restore ( ) ;
788+ await rm ( cwd , { recursive : true , force : true } ) ;
789+ }
790+ } ) ;
0 commit comments