From a8395b4e4c4be55b5812d8b30dd2c303da1f2c33 Mon Sep 17 00:00:00 2001 From: Afonso Coutinho Date: Tue, 2 Jun 2026 17:53:47 +0100 Subject: [PATCH] fix: agent_input_token_budget wrongly treated as a secret and unsettable from chat (#1294) * fix: don't classify agent_input_token_budget as a secret (token must be a suffix) * test: agent_input_token_budget is settable from chat --- src/tool_implementations.py | 9 ++++++++- tests/test_manage_settings_token_budget.py | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/test_manage_settings_token_budget.py diff --git a/src/tool_implementations.py b/src/tool_implementations.py index 95d39f1..3ad8d58 100644 --- a/src/tool_implementations.py +++ b/src/tool_implementations.py @@ -1527,7 +1527,14 @@ async def do_manage_settings(content: str, owner: Optional[str] = None) -> Dict: "tavily_api_key", "serper_api_key", "app_public_url", } def _is_secret(k): - return k in _SECRET_KEYS or any(t in k for t in ("api_key", "_key", "token", "secret", "password")) + # `token` must be a suffix, not a substring: otherwise the int + # setting `agent_input_token_budget` (which even has a "token budget" + # alias to set it from chat) is wrongly classified as a credential. + return ( + k in _SECRET_KEYS + or k.endswith("token") + or any(t in k for t in ("api_key", "_key", "secret", "password")) + ) # Friendly aliases → real keys, so natural phrasing resolves. _ALIASES_SET = { diff --git a/tests/test_manage_settings_token_budget.py b/tests/test_manage_settings_token_budget.py new file mode 100644 index 0000000..31fce6d --- /dev/null +++ b/tests/test_manage_settings_token_budget.py @@ -0,0 +1,22 @@ +"""Regression: agent_input_token_budget must be settable from chat (not flagged secret).""" +import asyncio +import json + +import src.settings as settings_mod +from src.tool_implementations import do_manage_settings + + +def test_set_token_budget_is_not_refused_as_secret(monkeypatch): + store = {} + monkeypatch.setattr(settings_mod, "load_settings", lambda: dict(store)) + monkeypatch.setattr(settings_mod, "save_settings", lambda s: store.update(s)) + + result = asyncio.run(do_manage_settings(json.dumps({ + "action": "set", "key": "agent_input_token_budget", "value": 8000, + }))) + + # The "token" substring used to flag this int setting as a credential and + # refuse to set it (even though there's a deliberate "token budget" alias). + assert "credential" not in result.get("response", "").lower(), result + assert result.get("exit_code") == 0, result + assert store.get("agent_input_token_budget") == 8000