fix(config): treat an empty providers list as no secret providers - #73

Merged
nadeem4 merged 1 commit into
mainfrom
fix/secrets-config-empty-providers
Aug 28, 2026
Merged

fix(config): treat an empty providers list as no secret providers#73
nadeem4 merged 1 commit into
mainfrom
fix/secrets-config-empty-providers

Conversation

@nadeem4

Copy link
Copy Markdown
Owner

The bug

The shipped configs/secrets.yaml ships with every provider example commented out:

version: 1providers:
# Example: Azure Key Vault# - id: azure-prod...

YAML parses that as {'version': 1, 'providers': None}. SecretsFileConfig.providers
was List[SecretProviderConfig] — required, no default — so validation failed and
ConfigManager.load_secrets raised:

ValueError: Secret Configuration Invalid: 1 validation error for SecretsFileConfig
providers
Input should be a valid list [type=list_type, input_value=None, input_type=NoneType]
For further information visit https://errors.pydantic.dev/2.13/v/list_type

NL2SQLContext.__init__ calls load_secrets, so the default non-demo path was
broken for anyone using the file as shipped
— including a fresh
pip install nl2sql-engine. It stayed invisible in demo mode only because
.env.demo points SECRETS_CONFIG at configs/secrets.demo.yaml, which does not
exist, and a missing file short-circuits to [].

Surfaced by the repaired nl2sql doctor, which reported the error instead of
crashing on it.

Which shapes failed

secrets.yaml shapebeforeafter
all-commented (the shipped file)FAILS: providers Input should be a valid listOK -> []
empty fileFAILS: Input should be a valid dictionary ... input_value=[]OK -> []
no providers: key at allFAILS: providers Field requiredOK -> []
providers: []OKOK -> []

Only the explicit empty list worked. All four are shapes a user legitimately
produces, and all four now load as "no secret providers configured".

The fix

Two defects, both real:

  1. configs/secrets.pyproviders now defaults to an empty list, plus a
    field_validator(..., mode="before") coercing None to []. In pydantic v2 a
    missing key and an explicit None are different cases: the default covers only
    the missing key, so the validator is what handles a providers: key left empty
    by commenting its entries out.
  2. configs/manager.pyyaml.safe_load(content) or [] produced a list
    for an empty file, which then failed model_validate with a confusing "should
    be a valid dictionary". The envelope is a mapping, so the fallback is now {}.

configs/secrets.yaml itself is deliberately left alone. Commenting out every
example is a legitimate thing for a user to do, and the loader has to tolerate it;
adding providers: [] to the shipped file would paper over the defect for that one
file and leave every user-edited copy broken.

Sibling loaders in manager.py were checked for the same fallback/model mismatch:
load_datasources, load_llm and load_sample_questions all use or {} against
mapping-shaped envelopes, and load_policies uses json.loads with no fallback.
None has the mismatch, and their remaining required fields (datasources,
default) are genuinely required — an empty datasources or LLM config is a
misconfiguration worth an error. Left as-is.

Tests

packages/nl2sql/tests/unit/test_config_manager_secrets_empty.py covers all four
shapes, that a real multi-provider config still parses, and — the regression that
actually shipped — that the repo's own configs/secrets.yaml loads without raising.
Written first; 4 of 6 failed against main with the ValidationError above.

Unit suite 247 passed, 1 skipped, 47 deselected (241 + 6 new), key-free
integration 28 passed, 267 deselected; both run twice under pytest-randomly.

@nadeem4
nadeem4 merged commit 97fe5d2 into mainAug 28, 2026
8 checks passed
@nadeem4
nadeem4 deleted the fix/secrets-config-empty-providers branch August 28, 2026 19:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@nadeem4
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(config): treat an empty providers list as no secret providers - #73

Merged
nadeem4 merged 1 commit into
mainfrom
fix/secrets-config-empty-providers
Aug 28, 2026
Merged

fix(config): treat an empty providers list as no secret providers#73
nadeem4 merged 1 commit into
mainfrom
fix/secrets-config-empty-providers

Conversation

@nadeem4

Copy link
Copy Markdown
Owner

The bug

The shipped configs/secrets.yaml ships with every provider example commented out:

version: 1providers:
# Example: Azure Key Vault# - id: azure-prod...

YAML parses that as {'version': 1, 'providers': None}. SecretsFileConfig.providers
was List[SecretProviderConfig] — required, no default — so validation failed and
ConfigManager.load_secrets raised:

ValueError: Secret Configuration Invalid: 1 validation error for SecretsFileConfig
providers
Input should be a valid list [type=list_type, input_value=None, input_type=NoneType]
For further information visit https://errors.pydantic.dev/2.13/v/list_type

NL2SQLContext.__init__ calls load_secrets, so the default non-demo path was
broken for anyone using the file as shipped
— including a fresh
pip install nl2sql-engine. It stayed invisible in demo mode only because
.env.demo points SECRETS_CONFIG at configs/secrets.demo.yaml, which does not
exist, and a missing file short-circuits to [].

Surfaced by the repaired nl2sql doctor, which reported the error instead of
crashing on it.

Which shapes failed

secrets.yaml shapebeforeafter
all-commented (the shipped file)FAILS: providers Input should be a valid listOK -> []
empty fileFAILS: Input should be a valid dictionary ... input_value=[]OK -> []
no providers: key at allFAILS: providers Field requiredOK -> []
providers: []OKOK -> []

Only the explicit empty list worked. All four are shapes a user legitimately
produces, and all four now load as "no secret providers configured".

The fix

Two defects, both real:

  1. configs/secrets.pyproviders now defaults to an empty list, plus a
    field_validator(..., mode="before") coercing None to []. In pydantic v2 a
    missing key and an explicit None are different cases: the default covers only
    the missing key, so the validator is what handles a providers: key left empty
    by commenting its entries out.
  2. configs/manager.pyyaml.safe_load(content) or [] produced a list
    for an empty file, which then failed model_validate with a confusing "should
    be a valid dictionary". The envelope is a mapping, so the fallback is now {}.

configs/secrets.yaml itself is deliberately left alone. Commenting out every
example is a legitimate thing for a user to do, and the loader has to tolerate it;
adding providers: [] to the shipped file would paper over the defect for that one
file and leave every user-edited copy broken.

Sibling loaders in manager.py were checked for the same fallback/model mismatch:
load_datasources, load_llm and load_sample_questions all use or {} against
mapping-shaped envelopes, and load_policies uses json.loads with no fallback.
None has the mismatch, and their remaining required fields (datasources,
default) are genuinely required — an empty datasources or LLM config is a
misconfiguration worth an error. Left as-is.

Tests

packages/nl2sql/tests/unit/test_config_manager_secrets_empty.py covers all four
shapes, that a real multi-provider config still parses, and — the regression that
actually shipped — that the repo's own configs/secrets.yaml loads without raising.
Written first; 4 of 6 failed against main with the ValidationError above.

Unit suite 247 passed, 1 skipped, 47 deselected (241 + 6 new), key-free
integration 28 passed, 267 deselected; both run twice under pytest-randomly.

@nadeem4
nadeem4 merged commit 97fe5d2 into mainAug 28, 2026
8 checks passed
@nadeem4
nadeem4 deleted the fix/secrets-config-empty-providers branch August 28, 2026 19:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@nadeem4
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(config): treat an empty providers list as no secret providers - #73

Merged
nadeem4 merged 1 commit into
mainfrom
fix/secrets-config-empty-providers
Aug 28, 2026
Merged

fix(config): treat an empty providers list as no secret providers#73
nadeem4 merged 1 commit into
mainfrom
fix/secrets-config-empty-providers

Conversation

@nadeem4

Copy link
Copy Markdown
Owner

The bug

The shipped configs/secrets.yaml ships with every provider example commented out:

version: 1providers:
# Example: Azure Key Vault# - id: azure-prod...

YAML parses that as {'version': 1, 'providers': None}. SecretsFileConfig.providers
was List[SecretProviderConfig] — required, no default — so validation failed and
ConfigManager.load_secrets raised:

ValueError: Secret Configuration Invalid: 1 validation error for SecretsFileConfig
providers
Input should be a valid list [type=list_type, input_value=None, input_type=NoneType]
For further information visit https://errors.pydantic.dev/2.13/v/list_type

NL2SQLContext.__init__ calls load_secrets, so the default non-demo path was
broken for anyone using the file as shipped
— including a fresh
pip install nl2sql-engine. It stayed invisible in demo mode only because
.env.demo points SECRETS_CONFIG at configs/secrets.demo.yaml, which does not
exist, and a missing file short-circuits to [].

Surfaced by the repaired nl2sql doctor, which reported the error instead of
crashing on it.

Which shapes failed

secrets.yaml shapebeforeafter
all-commented (the shipped file)FAILS: providers Input should be a valid listOK -> []
empty fileFAILS: Input should be a valid dictionary ... input_value=[]OK -> []
no providers: key at allFAILS: providers Field requiredOK -> []
providers: []OKOK -> []

Only the explicit empty list worked. All four are shapes a user legitimately
produces, and all four now load as "no secret providers configured".

The fix

Two defects, both real:

  1. configs/secrets.pyproviders now defaults to an empty list, plus a
    field_validator(..., mode="before") coercing None to []. In pydantic v2 a
    missing key and an explicit None are different cases: the default covers only
    the missing key, so the validator is what handles a providers: key left empty
    by commenting its entries out.
  2. configs/manager.pyyaml.safe_load(content) or [] produced a list
    for an empty file, which then failed model_validate with a confusing "should
    be a valid dictionary". The envelope is a mapping, so the fallback is now {}.

configs/secrets.yaml itself is deliberately left alone. Commenting out every
example is a legitimate thing for a user to do, and the loader has to tolerate it;
adding providers: [] to the shipped file would paper over the defect for that one
file and leave every user-edited copy broken.

Sibling loaders in manager.py were checked for the same fallback/model mismatch:
load_datasources, load_llm and load_sample_questions all use or {} against
mapping-shaped envelopes, and load_policies uses json.loads with no fallback.
None has the mismatch, and their remaining required fields (datasources,
default) are genuinely required — an empty datasources or LLM config is a
misconfiguration worth an error. Left as-is.

Tests

packages/nl2sql/tests/unit/test_config_manager_secrets_empty.py covers all four
shapes, that a real multi-provider config still parses, and — the regression that
actually shipped — that the repo's own configs/secrets.yaml loads without raising.
Written first; 4 of 6 failed against main with the ValidationError above.

Unit suite 247 passed, 1 skipped, 47 deselected (241 + 6 new), key-free
integration 28 passed, 267 deselected; both run twice under pytest-randomly.

@nadeem4
nadeem4 merged commit 97fe5d2 into mainAug 28, 2026
8 checks passed
@nadeem4
nadeem4 deleted the fix/secrets-config-empty-providers branch August 28, 2026 19:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@nadeem4
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(config): treat an empty providers list as no secret providers - #73

Merged
nadeem4 merged 1 commit into
mainfrom
fix/secrets-config-empty-providers
Aug 28, 2026
Merged

fix(config): treat an empty providers list as no secret providers#73
nadeem4 merged 1 commit into
mainfrom
fix/secrets-config-empty-providers

Conversation

@nadeem4

Copy link
Copy Markdown
Owner

The bug

The shipped configs/secrets.yaml ships with every provider example commented out:

version: 1providers:
# Example: Azure Key Vault# - id: azure-prod...

YAML parses that as {'version': 1, 'providers': None}. SecretsFileConfig.providers
was List[SecretProviderConfig] — required, no default — so validation failed and
ConfigManager.load_secrets raised:

ValueError: Secret Configuration Invalid: 1 validation error for SecretsFileConfig
providers
Input should be a valid list [type=list_type, input_value=None, input_type=NoneType]
For further information visit https://errors.pydantic.dev/2.13/v/list_type

NL2SQLContext.__init__ calls load_secrets, so the default non-demo path was
broken for anyone using the file as shipped
— including a fresh
pip install nl2sql-engine. It stayed invisible in demo mode only because
.env.demo points SECRETS_CONFIG at configs/secrets.demo.yaml, which does not
exist, and a missing file short-circuits to [].

Surfaced by the repaired nl2sql doctor, which reported the error instead of
crashing on it.

Which shapes failed

secrets.yaml shapebeforeafter
all-commented (the shipped file)FAILS: providers Input should be a valid listOK -> []
empty fileFAILS: Input should be a valid dictionary ... input_value=[]OK -> []
no providers: key at allFAILS: providers Field requiredOK -> []
providers: []OKOK -> []

Only the explicit empty list worked. All four are shapes a user legitimately
produces, and all four now load as "no secret providers configured".

The fix

Two defects, both real:

  1. configs/secrets.pyproviders now defaults to an empty list, plus a
    field_validator(..., mode="before") coercing None to []. In pydantic v2 a
    missing key and an explicit None are different cases: the default covers only
    the missing key, so the validator is what handles a providers: key left empty
    by commenting its entries out.
  2. configs/manager.pyyaml.safe_load(content) or [] produced a list
    for an empty file, which then failed model_validate with a confusing "should
    be a valid dictionary". The envelope is a mapping, so the fallback is now {}.

configs/secrets.yaml itself is deliberately left alone. Commenting out every
example is a legitimate thing for a user to do, and the loader has to tolerate it;
adding providers: [] to the shipped file would paper over the defect for that one
file and leave every user-edited copy broken.

Sibling loaders in manager.py were checked for the same fallback/model mismatch:
load_datasources, load_llm and load_sample_questions all use or {} against
mapping-shaped envelopes, and load_policies uses json.loads with no fallback.
None has the mismatch, and their remaining required fields (datasources,
default) are genuinely required — an empty datasources or LLM config is a
misconfiguration worth an error. Left as-is.

Tests

packages/nl2sql/tests/unit/test_config_manager_secrets_empty.py covers all four
shapes, that a real multi-provider config still parses, and — the regression that
actually shipped — that the repo's own configs/secrets.yaml loads without raising.
Written first; 4 of 6 failed against main with the ValidationError above.

Unit suite 247 passed, 1 skipped, 47 deselected (241 + 6 new), key-free
integration 28 passed, 267 deselected; both run twice under pytest-randomly.

@nadeem4
nadeem4 merged commit 97fe5d2 into mainAug 28, 2026
8 checks passed
@nadeem4
nadeem4 deleted the fix/secrets-config-empty-providers branch August 28, 2026 19:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@nadeem4
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(config): treat an empty providers list as no secret providers - #73

Merged
nadeem4 merged 1 commit into
mainfrom
fix/secrets-config-empty-providers
Aug 28, 2026
Merged

fix(config): treat an empty providers list as no secret providers#73
nadeem4 merged 1 commit into
mainfrom
fix/secrets-config-empty-providers

Conversation

@nadeem4

Copy link
Copy Markdown
Owner

The bug

The shipped configs/secrets.yaml ships with every provider example commented out:

version: 1providers:
# Example: Azure Key Vault# - id: azure-prod...

YAML parses that as {'version': 1, 'providers': None}. SecretsFileConfig.providers
was List[SecretProviderConfig] — required, no default — so validation failed and
ConfigManager.load_secrets raised:

ValueError: Secret Configuration Invalid: 1 validation error for SecretsFileConfig
providers
Input should be a valid list [type=list_type, input_value=None, input_type=NoneType]
For further information visit https://errors.pydantic.dev/2.13/v/list_type

NL2SQLContext.__init__ calls load_secrets, so the default non-demo path was
broken for anyone using the file as shipped
— including a fresh
pip install nl2sql-engine. It stayed invisible in demo mode only because
.env.demo points SECRETS_CONFIG at configs/secrets.demo.yaml, which does not
exist, and a missing file short-circuits to [].

Surfaced by the repaired nl2sql doctor, which reported the error instead of
crashing on it.

Which shapes failed

secrets.yaml shapebeforeafter
all-commented (the shipped file)FAILS: providers Input should be a valid listOK -> []
empty fileFAILS: Input should be a valid dictionary ... input_value=[]OK -> []
no providers: key at allFAILS: providers Field requiredOK -> []
providers: []OKOK -> []

Only the explicit empty list worked. All four are shapes a user legitimately
produces, and all four now load as "no secret providers configured".

The fix

Two defects, both real:

  1. configs/secrets.pyproviders now defaults to an empty list, plus a
    field_validator(..., mode="before") coercing None to []. In pydantic v2 a
    missing key and an explicit None are different cases: the default covers only
    the missing key, so the validator is what handles a providers: key left empty
    by commenting its entries out.
  2. configs/manager.pyyaml.safe_load(content) or [] produced a list
    for an empty file, which then failed model_validate with a confusing "should
    be a valid dictionary". The envelope is a mapping, so the fallback is now {}.

configs/secrets.yaml itself is deliberately left alone. Commenting out every
example is a legitimate thing for a user to do, and the loader has to tolerate it;
adding providers: [] to the shipped file would paper over the defect for that one
file and leave every user-edited copy broken.

Sibling loaders in manager.py were checked for the same fallback/model mismatch:
load_datasources, load_llm and load_sample_questions all use or {} against
mapping-shaped envelopes, and load_policies uses json.loads with no fallback.
None has the mismatch, and their remaining required fields (datasources,
default) are genuinely required — an empty datasources or LLM config is a
misconfiguration worth an error. Left as-is.

Tests

packages/nl2sql/tests/unit/test_config_manager_secrets_empty.py covers all four
shapes, that a real multi-provider config still parses, and — the regression that
actually shipped — that the repo's own configs/secrets.yaml loads without raising.
Written first; 4 of 6 failed against main with the ValidationError above.

Unit suite 247 passed, 1 skipped, 47 deselected (241 + 6 new), key-free
integration 28 passed, 267 deselected; both run twice under pytest-randomly.

@nadeem4
nadeem4 merged commit 97fe5d2 into mainAug 28, 2026
8 checks passed
@nadeem4
nadeem4 deleted the fix/secrets-config-empty-providers branch August 28, 2026 19:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@nadeem4
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(config): treat an empty providers list as no secret providers - #73

Merged
nadeem4 merged 1 commit into
mainfrom
fix/secrets-config-empty-providers
Aug 28, 2026
Merged

fix(config): treat an empty providers list as no secret providers#73
nadeem4 merged 1 commit into
mainfrom
fix/secrets-config-empty-providers

Conversation

@nadeem4

Copy link
Copy Markdown
Owner

The bug

The shipped configs/secrets.yaml ships with every provider example commented out:

version: 1providers:
# Example: Azure Key Vault# - id: azure-prod...

YAML parses that as {'version': 1, 'providers': None}. SecretsFileConfig.providers
was List[SecretProviderConfig] — required, no default — so validation failed and
ConfigManager.load_secrets raised:

ValueError: Secret Configuration Invalid: 1 validation error for SecretsFileConfig
providers
Input should be a valid list [type=list_type, input_value=None, input_type=NoneType]
For further information visit https://errors.pydantic.dev/2.13/v/list_type

NL2SQLContext.__init__ calls load_secrets, so the default non-demo path was
broken for anyone using the file as shipped
— including a fresh
pip install nl2sql-engine. It stayed invisible in demo mode only because
.env.demo points SECRETS_CONFIG at configs/secrets.demo.yaml, which does not
exist, and a missing file short-circuits to [].

Surfaced by the repaired nl2sql doctor, which reported the error instead of
crashing on it.

Which shapes failed

secrets.yaml shapebeforeafter
all-commented (the shipped file)FAILS: providers Input should be a valid listOK -> []
empty fileFAILS: Input should be a valid dictionary ... input_value=[]OK -> []
no providers: key at allFAILS: providers Field requiredOK -> []
providers: []OKOK -> []

Only the explicit empty list worked. All four are shapes a user legitimately
produces, and all four now load as "no secret providers configured".

The fix

Two defects, both real:

  1. configs/secrets.pyproviders now defaults to an empty list, plus a
    field_validator(..., mode="before") coercing None to []. In pydantic v2 a
    missing key and an explicit None are different cases: the default covers only
    the missing key, so the validator is what handles a providers: key left empty
    by commenting its entries out.
  2. configs/manager.pyyaml.safe_load(content) or [] produced a list
    for an empty file, which then failed model_validate with a confusing "should
    be a valid dictionary". The envelope is a mapping, so the fallback is now {}.

configs/secrets.yaml itself is deliberately left alone. Commenting out every
example is a legitimate thing for a user to do, and the loader has to tolerate it;
adding providers: [] to the shipped file would paper over the defect for that one
file and leave every user-edited copy broken.

Sibling loaders in manager.py were checked for the same fallback/model mismatch:
load_datasources, load_llm and load_sample_questions all use or {} against
mapping-shaped envelopes, and load_policies uses json.loads with no fallback.
None has the mismatch, and their remaining required fields (datasources,
default) are genuinely required — an empty datasources or LLM config is a
misconfiguration worth an error. Left as-is.

Tests

packages/nl2sql/tests/unit/test_config_manager_secrets_empty.py covers all four
shapes, that a real multi-provider config still parses, and — the regression that
actually shipped — that the repo's own configs/secrets.yaml loads without raising.
Written first; 4 of 6 failed against main with the ValidationError above.

Unit suite 247 passed, 1 skipped, 47 deselected (241 + 6 new), key-free
integration 28 passed, 267 deselected; both run twice under pytest-randomly.

@nadeem4
nadeem4 merged commit 97fe5d2 into mainAug 28, 2026
8 checks passed
@nadeem4
nadeem4 deleted the fix/secrets-config-empty-providers branch August 28, 2026 19:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@nadeem4
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(config): treat an empty providers list as no secret providers - #73

Merged
nadeem4 merged 1 commit into
mainfrom
fix/secrets-config-empty-providers
Aug 28, 2026
Merged

fix(config): treat an empty providers list as no secret providers#73
nadeem4 merged 1 commit into
mainfrom
fix/secrets-config-empty-providers

Conversation

@nadeem4

Copy link
Copy Markdown
Owner

The bug

The shipped configs/secrets.yaml ships with every provider example commented out:

version: 1providers:
# Example: Azure Key Vault# - id: azure-prod...

YAML parses that as {'version': 1, 'providers': None}. SecretsFileConfig.providers
was List[SecretProviderConfig] — required, no default — so validation failed and
ConfigManager.load_secrets raised:

ValueError: Secret Configuration Invalid: 1 validation error for SecretsFileConfig
providers
Input should be a valid list [type=list_type, input_value=None, input_type=NoneType]
For further information visit https://errors.pydantic.dev/2.13/v/list_type

NL2SQLContext.__init__ calls load_secrets, so the default non-demo path was
broken for anyone using the file as shipped
— including a fresh
pip install nl2sql-engine. It stayed invisible in demo mode only because
.env.demo points SECRETS_CONFIG at configs/secrets.demo.yaml, which does not
exist, and a missing file short-circuits to [].

Surfaced by the repaired nl2sql doctor, which reported the error instead of
crashing on it.

Which shapes failed

secrets.yaml shapebeforeafter
all-commented (the shipped file)FAILS: providers Input should be a valid listOK -> []
empty fileFAILS: Input should be a valid dictionary ... input_value=[]OK -> []
no providers: key at allFAILS: providers Field requiredOK -> []
providers: []OKOK -> []

Only the explicit empty list worked. All four are shapes a user legitimately
produces, and all four now load as "no secret providers configured".

The fix

Two defects, both real:

  1. configs/secrets.pyproviders now defaults to an empty list, plus a
    field_validator(..., mode="before") coercing None to []. In pydantic v2 a
    missing key and an explicit None are different cases: the default covers only
    the missing key, so the validator is what handles a providers: key left empty
    by commenting its entries out.
  2. configs/manager.pyyaml.safe_load(content) or [] produced a list
    for an empty file, which then failed model_validate with a confusing "should
    be a valid dictionary". The envelope is a mapping, so the fallback is now {}.

configs/secrets.yaml itself is deliberately left alone. Commenting out every
example is a legitimate thing for a user to do, and the loader has to tolerate it;
adding providers: [] to the shipped file would paper over the defect for that one
file and leave every user-edited copy broken.

Sibling loaders in manager.py were checked for the same fallback/model mismatch:
load_datasources, load_llm and load_sample_questions all use or {} against
mapping-shaped envelopes, and load_policies uses json.loads with no fallback.
None has the mismatch, and their remaining required fields (datasources,
default) are genuinely required — an empty datasources or LLM config is a
misconfiguration worth an error. Left as-is.

Tests

packages/nl2sql/tests/unit/test_config_manager_secrets_empty.py covers all four
shapes, that a real multi-provider config still parses, and — the regression that
actually shipped — that the repo's own configs/secrets.yaml loads without raising.
Written first; 4 of 6 failed against main with the ValidationError above.

Unit suite 247 passed, 1 skipped, 47 deselected (241 + 6 new), key-free
integration 28 passed, 267 deselected; both run twice under pytest-randomly.

@nadeem4
nadeem4 merged commit 97fe5d2 into mainAug 28, 2026
8 checks passed
@nadeem4
nadeem4 deleted the fix/secrets-config-empty-providers branch August 28, 2026 19:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@nadeem4
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(config): treat an empty providers list as no secret providers - #73

Merged
nadeem4 merged 1 commit into
mainfrom
fix/secrets-config-empty-providers
Aug 28, 2026
Merged

fix(config): treat an empty providers list as no secret providers#73
nadeem4 merged 1 commit into
mainfrom
fix/secrets-config-empty-providers

Conversation

@nadeem4

Copy link
Copy Markdown
Owner

The bug

The shipped configs/secrets.yaml ships with every provider example commented out:

version: 1providers:
# Example: Azure Key Vault# - id: azure-prod...

YAML parses that as {'version': 1, 'providers': None}. SecretsFileConfig.providers
was List[SecretProviderConfig] — required, no default — so validation failed and
ConfigManager.load_secrets raised:

ValueError: Secret Configuration Invalid: 1 validation error for SecretsFileConfig
providers
Input should be a valid list [type=list_type, input_value=None, input_type=NoneType]
For further information visit https://errors.pydantic.dev/2.13/v/list_type

NL2SQLContext.__init__ calls load_secrets, so the default non-demo path was
broken for anyone using the file as shipped
— including a fresh
pip install nl2sql-engine. It stayed invisible in demo mode only because
.env.demo points SECRETS_CONFIG at configs/secrets.demo.yaml, which does not
exist, and a missing file short-circuits to [].

Surfaced by the repaired nl2sql doctor, which reported the error instead of
crashing on it.

Which shapes failed

secrets.yaml shapebeforeafter
all-commented (the shipped file)FAILS: providers Input should be a valid listOK -> []
empty fileFAILS: Input should be a valid dictionary ... input_value=[]OK -> []
no providers: key at allFAILS: providers Field requiredOK -> []
providers: []OKOK -> []

Only the explicit empty list worked. All four are shapes a user legitimately
produces, and all four now load as "no secret providers configured".

The fix

Two defects, both real:

  1. configs/secrets.pyproviders now defaults to an empty list, plus a
    field_validator(..., mode="before") coercing None to []. In pydantic v2 a
    missing key and an explicit None are different cases: the default covers only
    the missing key, so the validator is what handles a providers: key left empty
    by commenting its entries out.
  2. configs/manager.pyyaml.safe_load(content) or [] produced a list
    for an empty file, which then failed model_validate with a confusing "should
    be a valid dictionary". The envelope is a mapping, so the fallback is now {}.

configs/secrets.yaml itself is deliberately left alone. Commenting out every
example is a legitimate thing for a user to do, and the loader has to tolerate it;
adding providers: [] to the shipped file would paper over the defect for that one
file and leave every user-edited copy broken.

Sibling loaders in manager.py were checked for the same fallback/model mismatch:
load_datasources, load_llm and load_sample_questions all use or {} against
mapping-shaped envelopes, and load_policies uses json.loads with no fallback.
None has the mismatch, and their remaining required fields (datasources,
default) are genuinely required — an empty datasources or LLM config is a
misconfiguration worth an error. Left as-is.

Tests

packages/nl2sql/tests/unit/test_config_manager_secrets_empty.py covers all four
shapes, that a real multi-provider config still parses, and — the regression that
actually shipped — that the repo's own configs/secrets.yaml loads without raising.
Written first; 4 of 6 failed against main with the ValidationError above.

Unit suite 247 passed, 1 skipped, 47 deselected (241 + 6 new), key-free
integration 28 passed, 267 deselected; both run twice under pytest-randomly.

@nadeem4
nadeem4 merged commit 97fe5d2 into mainAug 28, 2026
8 checks passed
@nadeem4
nadeem4 deleted the fix/secrets-config-empty-providers branch August 28, 2026 19:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@nadeem4