@@ -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,107 @@ 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 are 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+ } finally {
721+ stderr . restore ( ) ;
722+ await rm ( cwd , { recursive : true , force : true } ) ;
723+ }
724+ } ) ;
725+
726+ test ( "settings load does not warn for a normal provider block" , async ( ) => {
727+ const cwd = await mkdtemp ( join ( tmpdir ( ) , "ic-unit-config-no-ignored-" ) ) ;
728+ const path = join ( cwd , "settings.json" ) ;
729+ const stderr = captureStderr ( ) ;
730+ try {
731+ await writeFile (
732+ path ,
733+ JSON . stringify ( {
734+ providers : {
735+ external : {
736+ baseURL : "https://external.example/v1" ,
737+ keyless : true ,
738+ models : [ "test-model" ] ,
739+ } ,
740+ } ,
741+ } ) ,
742+ ) ;
743+ const loaded = await loadSettings ( path ) ;
744+ expect ( loaded ) . not . toBeNull ( ) ;
745+ expect ( stderr . output ( ) ) . not . toContain ( "not honored by Corbits" ) ;
746+ expect ( stderr . output ( ) ) . not . toContain ( "supportsThinkingTokenBudget" ) ;
747+ } finally {
748+ stderr . restore ( ) ;
749+ await rm ( cwd , { recursive : true , force : true } ) ;
750+ }
751+ } ) ;
752+
753+ // CL-10226 (Part 1): the same warn must surface on the config path (--config /
754+ // global settings), which routes through loadSettings... → normalizeParsedSettings.
755+ test ( "loadConfig warns and still loads when a provider carries ignored fields" , async ( ) => {
756+ const cwd = await mkdtemp ( join ( tmpdir ( ) , "ic-unit-config-ignored-config-" ) ) ;
757+ const globalSettingsPath = join ( cwd , "global.json" ) ;
758+ const stderr = captureStderr ( ) ;
759+ try {
760+ await writeFile (
761+ globalSettingsPath ,
762+ JSON . stringify ( {
763+ defaultProvider : "external" ,
764+ providers : {
765+ external : {
766+ baseURL : "https://external.example/v1" ,
767+ apiKey : "test-key" ,
768+ models : [ "test-model" ] ,
769+ samplingParams : { temperature : 0.5 } ,
770+ } ,
771+ } ,
772+ } ) ,
773+ ) ;
774+ const { impl } = offlineFetch ( ) ;
775+ const config = await loadConfig ( [ "--cwd" , cwd , "do something" ] , {
776+ globalSettingsPath,
777+ pricing : { fetchImpl : impl } ,
778+ } ) ;
779+ expect ( config . configured ) . toBe ( true ) ;
780+ if ( config . configured ) expect ( config . providerName ) . toBe ( "external" ) ;
781+ expect ( stderr . output ( ) ) . toContain ( "not honored by Corbits" ) ;
782+ expect ( stderr . output ( ) ) . toContain ( "samplingParams" ) ;
783+ } finally {
784+ stderr . restore ( ) ;
785+ await rm ( cwd , { recursive : true , force : true } ) ;
786+ }
787+ } ) ;
0 commit comments