Chanto picks up what you've already configured for Claude Code and Codex, so a project set up for either works without changes. This compatibility runs one way: Chanto reads their files and leaves them as they are. Run /config to see which files were loaded, and chanto --dump-config to print the merged result.
| Claude Code | Codex | |
|---|---|---|
| Settings | ~/.claude/settings.json, .claude/settings.json, .claude/settings.local.json: permissions (allow/deny/ask, defaultMode, additionalDirectories), hooks, disableAllHooks, env, statusLine, bashEditDiffEnabled (bash_diff); the env variable CLAUDE_BASH_NO_LOGIN=1 turns off the shell snapshot | ~/.codex/config.toml, .codex/config.toml: mcp_servers, model_providers, [hooks]; hooks.json beside them; trusted projects |
| MCP servers | .mcp.json, ~/.claude.json (user and per-project, including disabledMcpServers), with ${VAR} / ${VAR:-default} expansion; .mcp.json approval keys enabledMcpjsonServers, disabledMcpjsonServers, enableAllProjectMcpServers; allowedMcpServers/deniedMcpServers; chanto mcp add-json and add-from-claude-desktop | [mcp_servers.*] (stdio and HTTP: command, args, env, env_vars, cwd, url, http_headers, env_http_headers, bearer_token_env_var, startup_timeout_sec/_ms, tool_timeout_sec, enabled_tools, disabled_tools, enabled) |
| Managed settings | managed-settings.json and managed-settings.d/*.json (alphabetical) in /Library/Application Support/ClaudeCode/ (macOS), /etc/claude-code/ (Linux, WSL), C:\Program Files\ClaudeCode\ (Windows): permissions (allow/deny/ask, additionalDirectories, defaultMode, disableBypassPermissionsMode), env, allowManagedPermissionRulesOnly, allowedMcpServers/deniedMcpServers, allowManagedMcpServersOnly | — |
| Instructions | CLAUDE.md, .claude/CLAUDE.md, CLAUDE.local.md, ~/.claude/CLAUDE.md, @path imports, .claude/rules/**/*.md (including paths:-scoped rules) | AGENTS.md, AGENTS.override.md, ~/.codex/AGENTS.md (Gemini, Cursor, Copilot, Aider and opencode files: below) |
| Skills, commands, agents | .claude/skills, .claude/commands (subfolders namespace them: commands/frontend/test.md is /frontend:test), .claude/agents (subfolders allowed), in home and in every directory from the repository root down to the working directory | ~/.codex/skills, ~/.codex/prompts (/name or /prompts:name, $1..$9, $NAME), .agents/skills, .codex/skills |
| Plugins | chanto-managed installs and Claude Code plugins enabled in enabledPlugins: commands (/plugin:cmd, or /cmd when unambiguous), agents, skills, hooks (${CLAUDE_PLUGIN_ROOT}, ${CLAUDE_PLUGIN_DATA}), MCP servers and LSP servers, including components defined in the marketplace entry. /plugins lists them | — |
| Tools | — | apply_patch patches (as a tool call or apply_patch <<EOF in the shell), shell argv calls, update_plan |
| CLI | -p, -c, -r, --add-dir, --dangerously-skip-permissions, --fallback-model, --permission-mode, --allowed-tools/--disallowed-tools, --output-format, --input-format stream-json, --system-prompt[-file], --append-system-prompt[-file], --mcp-config, --strict-mcp-config, --settings, --setting-sources, --session-id, -n/--name, --no-session-persistence, --json-schema, --agent, --agents (JSON; a number is Chanto's team size), --tools, --plugin-dir, --bare, --permission-prompt-tool, --include-partial-messages (no-op); see Claude Code's flags | exec, --full-auto, --yolo, -s read-only|workspace-write|danger-full-access, -C, --json |
How it works:
- Instruction files are read from your home directory and from every directory between the repository root and the working directory. Files in deeper subdirectories are added to the context the first time the agent reads or edits something there.
- A skill, command or subagent nearer the working directory replaces one with the same name further up, and project ones replace your home ones.
- Skills follow Claude Code's format:
- Every skill also runs as
/name [args]. When a skill and a command share a name, the skill wins (built-in commands always win).user-invocable: falsehides a skill from/;disable-model-invocation: truehides it from the model and theSkilltool, so only/nameruns it. - In
SKILL.md,$ARGUMENTSis all the arguments, and$ARGUMENTS[N]or$Nis one of them, counting from 0 ($0is the first; command files keep$1as the first).${CLAUDE_SKILL_DIR}is the skill's directory. Arguments that no placeholder takes are appended asARGUMENTS: <value>.!`cmd`at the start of a line or after a space is replaced by the command's output before the model sees it, in skills and commands alike; project skills and commands don't run them in a folder you haven't trusted. Arguments are filled in after the blocks run, so they can't add a block or a command. Inside a block they are data: all of them are in theARGUMENTSenvironment variable ("$ARGUMENTS", or$env:ARGUMENTSin PowerShell); in a command file's blocks run by a POSIX shell, the words are also"$1".."$9", and aNAME=valueargument is$NAMEwhen the block uses it and the environment has noNAMEalready. Quote them, as in!`git log "$1"`. context: forkruns the skill in a subagent (agent:picks the type, defaultgeneral-purpose). It runs in the background and its report arrives when it finishes;background: falsewaits for it in the turn that invoked the skill, and so do non-interactive runs (-p).- Skills and command files also read
argument-hint(shown next to/namein completion and/help),allowed-tools(permission rules, space- or comma-separated or a list, that are pre-approved during the turn that invokes it; deny and ask rules still win, and the grant clears with your next message),modelandeffort(for the rest of that turn; withcontext: fork, for the subagent),shell(bashorpowershellfor its!`cmd`blocks) andhooks(registered when it's invoked and kept for the rest of the session;once: trueremoves a hook after its first successful run; a project skill's hooks need folder trust). - Boolean fields accept
true/false,yes/no,on/offand1/0in any case.
- Every skill also runs as
- Subagent files read Claude Code's frontmatter; unknown keys are ignored:
toolsanddisallowedToolsare a comma-separated string or a YAML list. AdisallowedToolsentry likeBash(git push *)removes the whole tool. If Chanto can't read either value, the subagent gets no tools rather than all of them.model: inherituses the main conversation's model.sonnet,opus,haikuandfablebecome the newest Claude model of that family when the subagent's provider is the Anthropic API (anthropic/…or akind = "anthropic"provider). On other providers, including Bedrock, Vertex AI and Foundry, the subagent keeps its usual model and Chanto warns once. Any other value is a model spec.permissionMode(default/manual,acceptEdits,auto,dontAsk,plan,bypassPermissions) applies while the main conversation is in default or plan mode; in bypass, acceptEdits or auto mode the subagent runs in that mode, and it never gets bypass on its own.dontAskdenies anything that would prompt.maxTurnscaps its model calls; at the limit its last message comes back marked partial, and it can be resumed.skillspreloads up to 32 skills' full content into its prompt (skills withdisable-model-invocationcan't be preloaded; missing ones are skipped).mcpServerslists configured server names (kept reachable through atoolslist) and inline definitions (- name: {type, command, args, url, ...}), which connect when the subagent starts and close when it's done. A project agent's inline servers and itshooksneed folder trust.hooks(same layout as settings) run only while the subagent does;Stophooks fire asSubagentStop.memory: user|project|localgives it a directory (~/.claude/agent-memory/<name>/,.claude/agent-memory/<name>/,.claude/agent-memory-local/<name>/) with instructions and the first 200 lines of itsMEMORY.mdin its prompt, and adds Read, Write and Edit to its tools. Off when[memory] auto = false.background: truealways runs it in the background,isolation: worktreein its own git worktree,effortsets its effort level,colorcolors its name in the result line, andomitClaudeMd: trueleaves the instruction files out of its prompt.- With
--agent <name>the definition runs as the main session: its tools, model, prompt,permissionMode,maxTurns,effort,skills,mcpServers,hooksandmemoryapply, andinitialPromptbecomes the first message of a new session (ahead of any prompt you pass).--agents '<json>'takes the same fields under the same keys.
- Frontmatter is read as YAML: quoted strings,
>and|blocks, lists, nested maps (hooks:,mcpServers:), flow[...]/{...}collections and comments. - Permission rules follow Claude Code's rules:
Edit(...)covers every tool that writes files andRead(...)every tool that reads them.- In paths,
//means absolute,~/means home, a leading/is relative to the project and./is relative to the working directory. mcp__servermatches all of that server's tools.- A bare
Bash(orBash(*)) allow rule covers every command, including ones with$(…), backticks or<(…). A prefix rule such asBash(git:*)doesn't cover those, since the substituted command isn't visible in the text:git $(rm -rf ~)still needs approval. Deny and ask rules win over either. - When a non-interactive run (
-p) refuses a call that needs approval, the message suggests the allow rule that "don't ask again" would add for it, such asBash(npm run test:*)orBash(git push:*).
- Imported MCP servers that need an OAuth login show as "needs login" (
/mcp login <name>); ones that fail to connect for other reasons are skipped with a single warning. - Project-level files go through the same folder trust check as
.chanto/config.toml. If you already trusted a folder in Claude Code or Codex, Chanto trusts it too. - To turn imports off, set
compat = { claude = false, codex = false }in~/.chanto/config.toml. - Claude Code managed settings (your organization's policy) are read whatever
compatsays and applied above every other layer, profiles included: their deny and ask rules can't be removed by any other file, and theirenvwins. WithallowManagedPermissionRulesOnly: true, only the policy's own allow rules count (deny and ask rules from your files still apply).permissions.defaultModesets the mode every session starts in, over your config and command-line flags (you can still switch modes during a session).permissions.disableBypassPermissionsMode: "disable"rules out bypass mode entirely:--dangerously-skip-permissions, a configmode,/mode bypass, ACP and the HTTP API all fall back to default mode with a warning. Other managed keys are ignored. A managed file that isn't a JSON object stops Chanto from starting, as it does Claude Code;chanto doctorlists the managed files among the config layers. Debug builds acceptCHANTO_MANAGED_SETTINGS_DIRto test a policy from another directory; release builds always read the real one. MCP_TOOL_TIMEOUT(milliseconds) limits MCP tool calls as in Claude Code, for servers withouttool_timeout_secwhenmcp_tool_timeoutisn't set; unset, the limit is 10 minutes.- MCP tool search follows Claude Code's
ToolSearch: the same query forms,ENABLE_TOOL_SEARCHvalues (true,false,auto,auto:N),alwaysLoadserver setting andanthropic/alwaysLoadtool metadata. Two differences: unset meansauto(Claude Code always defers on its own API), since small local models do better with tools in view while the list is small; and loaded definitions are sent as ordinary tools appended to the list (and in theToolSearchresult) rather than astool_referenceblocks, which only Anthropic's API supports. See MCP servers. --fallback-modelandfallback_modelwork like Claude Code's--fallback-modelandfallbackModel(a chain of up to three, tried in order, for the current turn only, with a notice when it switches). Chanto also switches when a local model server refuses the connection, which Claude Code treats as a transport error. Claude Code'sfallbackModelsetting isn't imported, since its model names don't apply to other providers. See Fallback models.compat.codex_rules = truealso imports the command approvals in~/.codex/rules/*.rules(prefix_rule) as Bash allow, ask and deny rules.
Other tools' instruction files
Chanto also reads the instruction files other coding tools keep, in each directory from the repository root down to the working directory:
| Tool | Files | Switch |
|---|---|---|
| Gemini CLI | ~/.gemini/GEMINI.md, GEMINI.md (hierarchical: subdirectory files load once the agent works there, like AGENTS.md) | compat.gemini |
| Cursor | .cursor/rules/**/*.mdc (and .md): alwaysApply: true always loads; globs loads the rule when the agent touches a matching file; a rule with only a description is listed for the model to read when relevant; manual rules aren't loaded. Legacy .cursorrules always loads | compat.cursor |
| GitHub Copilot | .github/copilot-instructions.md; .github/instructions/**/*.instructions.md by applyTo (** = always, other globs = when a matching file is touched) | compat.copilot |
| Aider | the files .aider.conf.yml lists under read: (e.g. CONVENTIONS.md); nothing else | compat.aider |
| opencode | ~/.config/opencode/AGENTS.md; instructions paths and globs in opencode.json; subagents in .opencode/agent/*.md and ~/.config/opencode/agent/ (mode: primary agents are skipped; tools: { write: false } entries become disallowed tools) | compat.opencode |
Project files from these tools load only in a trusted folder; user-level files always load. Paths they reference must stay inside the project. Files with identical content (an AGENTS.md symlinked as GEMINI.md, the same text in two tools' files) load once. All are on by default; turn one off in ~/.chanto/config.toml, e.g. compat = { cursor = false }. /config lists the instruction files loaded and the path-scoped rules waiting for a match.
Hooks
Hooks follow Claude Code's protocol for its events and handler types. Hooks written for Claude Code or Codex work unchanged.
Events: all of Claude Code's events are accepted. Each fires at the matching moment in Chanto, with Claude Code's input fields, matcher and decision handling:
PreToolUse,PostToolUse,PostToolUseFailure,PermissionRequest,UserPromptSubmit,Stop,SubagentStart,SubagentStop,SessionStart,SessionEnd: as in Claude Code.Notificationfires for permission prompts and, withnotification_type: "idle_prompt", a minute after a turn ends while you seem to be away (nothing typed, nothing running in the background; the TUI also needs the terminal unfocused).PreCompact/PostCompact: around/compact(manual) and automatic compaction (auto). Exit 2 ordecision: "block"onPreCompactskips the compaction.PostCompactgetscompact_summary.PermissionDenied: auto mode refused a tool call (the classifier or a dangerous-command check).hookSpecificOutput.retry: truetells the model it may retry, except when the classifier was unavailable or timed out.PostToolBatch: after every call of a batch has its result, before the next model call.tool_callslists each call'stool_name,tool_input,tool_use_idandtool_response(the text the model receives).additionalContextreaches the model;decision: "block"orcontinue: falseends the turn.StopFailure: the turn ended on a model request error.erroris Claude Code's error type, guessed from the provider's error text (rate_limit,authentication_failed,billing_error,model_not_found,server_error, ...), pluserror_detailsandlast_assistant_message. Output is ignored.MessageDisplay: the assistant's text passes through the hook before it's shown, in batches of whole lines (the whole message at once in-pand other non-interactive runs).hookSpecificOutput.displayContentreplaces what's displayed; the conversation keeps the original. Default timeout 10 seconds.UserPromptExpansion: a typed/commandexpands into a prompt: custom commands and skills (expansion_type: "slash_command",command_sourceprojectoruser) and MCP prompts (mcp_prompt). Exit 2 ordecision: "block"stops it;additionalContextgoes along with the prompt. Built-in commands such as/reviewdon't fire it.TaskCreated/TaskCompleted: Chanto's tasks are theTodoWritelist. A new item is a task being created; an item turningcompletedis a task being completed.task_idis the item's position,task_subjectits text. A block keeps that item out of the list, or keeps it from being marked completed, and the hook's message goes back to the model as the tool's error.TeammateIdle: a/teamsub-task's run finished, before the lead reviews it (teammate_nameis the sub-task id,team_namethe team id). Exit 2 runs the sub-task again with the hook's message, told to pick up its earlier branch first (up to five runs per sub-task);continue: falsestops it.InstructionsLoaded: instruction files (AGENTS.md,CLAUDE.md,CHANTO.md,.claude/rules/*.md) loaded at session start (load_reason: "session_start",compactafter a compaction) and nested ones loaded when the agent touches a subdirectory (nested_traversal, orpath_glob_matchfor rules withpaths:). Unlike Claude Code it also fires forAGENTS.md, Chanto's own instruction file. Runs in the background.ConfigChange: Chanto applies changed settings when you run/reload, so that's when it fires, once per settings file changed since they were last loaded (user_settings,project_settings,local_settings,policy_settings,skills; Chanto'sconfig.tomlfiles count with the matching Claude settings). A block keeps the current settings, except forpolicy_settings. Unlike Claude Code,/reloadsays it was blocked.CwdChanged: a Bash command in the main conversation changed the session's working directory (old_cwd,new_cwd).watchPathssets the filesFileChangedwatches. Its hooks getCLAUDE_ENV_FILE(see below); what they write there lasts until the nextCwdChanged.FileChanged: the files named in theFileChangedmatchers (in the working directory) and anywatchPathsaSessionStart,CwdChangedorFileChangedhook returned are polled twice a second. A change, creation or deletion fires the hooks whose matcher fits the file name, withfile_pathandevent(change,add,unlink). Its hooks get the sameCLAUDE_ENV_FILEasCwdChangedhooks.DirectoryAdded:/add-diradded a directory that wasn't already in the workspace (source: "slash_command"). Runs in the background (600-second default timeout); asystemMessagereaches the model with the next prompt.--add-dirdoesn't fire it, as in Claude Code.WorktreeCreate/WorktreeRemove: aWorktreeCreatehook replaces git for--worktreesessions and for subagents started withisolation: "worktree"; it getsnameand prints the directory's path as the last line of stdout (an http hook returnshookSpecificOutput.worktreePath). A failure, or no usable directory, fails the creation. On leaving a--worktreesession whose worktree a hook made, Chanto removes it when git sees no changes in it: through theWorktreeRemovehooks (worktree_path; a non-zero exit fails the removal if the directory is still there), or without one, asgit worktree remove --forcewould. Subagents' hook-made worktrees are kept, as in Claude Code.PreModelSwitch/PostModelSwitch:/model <name>(source: "command") and an ACP client's model change (sdk). The matcher sees the model name without itsprovider/prefix.PreModelSwitchcan refuse the switch (exit 2,decision: "block",permissionDecision: "deny", or not answering within its 30-second timeout) or ask (ask:/modelasks you to confirm; elsewhere it refuses).PostModelSwitchalso fires when resuming a session restores its model (resume); its stdout oradditionalContextreaches the model with the next prompt.context_tokensis an estimate,prompt_cache_warmmeans a model reply arrived in the last five minutes, andestimated_cache_write_usduses Chanto's price list (0 withpricing: "default"for models it can't price).Elicitation/ElicitationResult: around an MCP server'selicitation/create, matched on the server name. AnElicitationhook can answer for you (hookSpecificOutput.actionacceptwithcontent,declineorcancel); exit 2 ordecision: "block"declines. Otherwise you answer, andElicitationResulthooks can change or block your answer the same way. Hooks also run in-pmode, where nobody is there to answer and requests are otherwise declined.Setup: fires only forchanto --init-only(runs theSetuphooks with triggerinit, then theSessionStarthooks, and exits without a conversation) and forchanto -p --init/-p --maintenance(triggerinit/maintenance, before the prompt runs; without-pthese flags are an error). The matcher and thetriggerinput field are the trigger. Onlycommandhooks run; Setup can't block, any exit code continues, and its JSON output is discarded.
Codex's
Interruptisn't fired. Hooks configured for unknown events produce a startup warning.Handler types: all of Claude Code's:
command,http,mcp_tool,promptandagent, on the events Claude Code allows them (others are skipped with a single warning). A handler of an unknown type, or without the fields its type needs, is reported at startup and bychanto doctor.command:argsswitches to exec form (commandis spawned directly withargs, no shell;${CLAUDE_PROJECT_DIR},${CLAUDE_PLUGIN_ROOT}and${CLAUDE_PLUGIN_DATA}are substituted).shell: "powershell"runs it with PowerShell (pwshwhere installed outside Windows).async: trueruns it in the background without blocking or a timeout; when it exits, itsadditionalContextandsystemMessagereach the model on its next turn.asyncRewake: truedoes the same within its timeout, and exit code 2 also wakes an idle session with the hook's stderr (or stdout).mcp_tool:server,toolandinput, whose strings take${path}values from the hook input ("${tool_input.file_path}"). The tool's text is read like a command's stdout; an error result, or a server that's missing or not connected, is a non-blocking error. Not run forSetuporSessionStartat launch or resume.prompt: one model call (the hook'smodel, or the session's) with the prompt, where$ARGUMENTSis the hook input JSON (appended when absent;\$is a literal$). The model answers{"ok": true}or{"ok": false, "reason": "..."}.ok: falsekeeps aStop/SubagentStopagent working (unless it also says"impossible": true), ends the turn onPreToolUseandPostToolUsewith the reason as a warning (continueOnBlock: trueinstead returns it to the model: a denied tool call, or feedback), blocksUserPromptSubmit,UserPromptExpansion,PostToolBatch,PostToolUseFailure,TaskCreatedandTaskCompleted, and does nothing onPermissionRequestandPermissionDenied. Default timeout 30 seconds.agent: likeprompt, but a read-only subagent (Read, Grep, Glob, Ls; up to 50 model calls, no hooks of its own) investigates before answering, andok: falseacts ascontinueOnBlock: true. Not onPermissionRequest. Default timeout 60 seconds.
onFailure: "block"(command and http hooks): a hook that can't start, exits with a code other than 0 or 2, times out or prints invalid JSON does what exit 2 does on that event (denies onPermissionRequest), withfailed; blocking because onFailure is "block"(ortimed out) in the message. No effect onStop,SubagentStop,TaskCompleted,TeammateIdleand background hooks.disableAllHooks: true(ordisable_all_hooks = true) turns all hooks off, skill and subagent hooks included. A project setting needs folder trust.Hooks in skills and subagents: see their frontmatter above.
once: trueis honored for skill hooks only.Matchers: empty or
*matches everything. A value made only of letters, digits,_,-, spaces,,and|is a list of exact names (Edit|WriteorEdit, Write). Anything else is an unanchored regex (^Notebook,mcp__memory__.*). Matchers that nameEdit,WriteorMultiEditalso matchapply_patch.if: a handler can carry one permission rule, such as"if": "Bash(git *)"or"if": "Edit(**/*.ts)". The hook runs only when the tool call matches that rule, using the same matcher as permission rules. For Bash, every subcommand is checked, including commands inside$(...)and backticks, with leadingVAR=valueassignments ignored. When Chanto can't tell what a command runs (nested substitutions), the hook runs, as in Claude Code.ifapplies to tool events only. A hook whoseifis malformed or set on another event is skipped, with a single warning.Input: a command hook gets JSON on stdin with
hook_event_name,session_id,transcript_path,cwdandpermission_mode, plus the event's own fields:- Tool events:
tool_nameandtool_input. PostToolUse: atool_responseobject. Bash's isstdout,stderr,interruptedandisImage, with stdout and stderr interleaved instdout. Write's istype,filePathandcontent. Edit's isfilePath,oldString,newStringandreplaceAll. Read's istypeandfile.filePath/file.content. Other tools get{"output": ...}. The plain text the model saw is also sent astool_output.PostToolUseFailure:errorandis_interrupt.Stop/SubagentStop:last_assistant_message(also sent aslast_messagefor older hooks) andstop_hook_active.
The environment has
CLAUDE_PROJECT_DIRandCHANTO_PROJECT_DIR. Payload fields Claude Code sends that Chanto doesn't include:tool_use_id(sent onPermissionDeniedandPostToolBatchonly),duration_ms,agent_id,background_tasks,prompt_id,effort.- Tool events:
Persisted environment (
CLAUDE_ENV_FILE):SessionStart,Setup,CwdChangedandFileChangedcommand hooks getCLAUDE_ENV_FILE(also asCHANTO_ENV_FILE), a per-session file under~/.chanto/session-env/that's kept when you resume. Other events don't get it. Appendexport NAME=valuelines to it, and every later Bash command in the session (background ones and subagents' too) runs the file first, so a line can use anything Bash evaluates, such asexport PATH="$PATH:./node_modules/.bin". WhatCwdChangedandFileChangedhooks write lasts until the nextCwdChanged. ACLAUDE_ENV_FILEset before Chanto starts is run before each command as well. Sandboxed commands can read the files. Commands in a background run's container don't get them. Where the shell is PowerShell or cmd (Windows without Git Bash, orCHANTO_SHELL), nothing runs the files and hooks don't getCLAUDE_ENV_FILE, as in Claude Code. Variables from a launch-timeCLAUDE_ENV_FILEare still copied into the command's environment if every line of the files is blank, a#comment or a plain assignment (export NAME=value,declare -x NAME=valueorNAME=value, with unquoted[A-Za-z0-9_@%+=:,./-]text, single-quoted text, or double-quoted text with$, backticks and"escaped). If any line is something else ($PATH,source,$'...'), none are copied and the debug log saysSession environment is not all plain assignments. On Windows, values containing/or\are left out, and sandboxed commands get none.HTTP hooks:
{"type": "http", "url": "...", "headers": {...}, "allowedEnvVars": [...], "timeout": 30}POSTs the same JSON payload.$VAR/${VAR}in headers expand only for variables listed inallowedEnvVars; unlisted ones become empty. A 2xx response with an empty body counts as success. A 2xx response with a JSON object body is read like a command hook's stdout. Any other body, a non-2xx status or a connection error is a non-blocking error (or a block withonFailure: "block"), and a timeout cancels the hook. Like command hooks, http hooks don't run onSessionStartandSetup.Blocking: exit code 2 blocks, and stderr (or stdout, if stderr is empty) is passed back to the model. A hook that blocks or denies without a reason is named instead:
hook [<command>] exited with code 2 and gave no reason.JSON output: a hook can print JSON on stdout to approve, deny or ask (
hookSpecificOutput.permissionDecision, ordecision.behaviorforPermissionRequest). It can also rewrite the tool input (updatedInput), add context (additionalContext), make the agent keep going (decision: "block"onStop), stop the turn (continue: false) or show a message (systemMessage). OnPostToolUse,updatedToolOutputreplaces what the model sees when it's shaped like the tool'stool_response(Bash'sstdout/stderr, Read'sfile.content, other tools'output);updatedMCPToolOutputdoes the same for MCP tools only, which also accept text or content blocks. The transcript display keeps the original.suppressOutputis ignored.Timeouts: 60 seconds by default for command, http and mcp_tool hooks (Claude Code's default is longer), except
MessageDisplay(10),PreModelSwitch(30) andDirectoryAdded(600);prompt30 andagent60. Hooks for one event run one after another, not in parallel.Config: hooks go in TOML or JSON. In TOML, use
[[hooks.PreToolUse]]withmatcher,commandortype = "http"plusurl, and optionalifandtimeout_secs. In JSON, use Claude's layout in.claude/settings.json. Codex hooks are also read:~/.codex/hooks.jsonand[hooks]in~/.codex/config.toml, plus.codex/hooks.jsonand.codex/config.tomlin the project. Codex hooks are not read whencompat.codexis off or Codex's[features] hooks = false(orcodex_hooks = false) is set. Project hooks from any source need folder trust. Chanto doesn't check Codex's per-hook review state./hookslists configured hooks.Plugins: plugin hooks and stdio MCP servers get
CLAUDE_PLUGIN_ROOT(the plugin directory) andCLAUDE_PLUGIN_DATAin their environment.CLAUDE_PLUGIN_DATAis a per-plugin directory,~/.chanto/plugin-data/<plugin>, created when first needed.${CLAUDE_PLUGIN_ROOT}and${CLAUDE_PLUGIN_DATA}are substituted in their definitions.
Format and lint after edits
After Write, Edit, MultiEdit or apply_patch succeeds, Chanto runs each changed file's formatter (in place), then its linter, from the project root. The model is told what the formatter changed, as a small diff (or "Read it again" for large rewrites), so its view of the file stays right, and the formatter's write doesn't count as an outside change for the next edit. A linter that exits non-zero sends its output to the model as <lint file="..." command="...">; the transcript shows a one-line summary. A formatter that fails leaves the file as written and says why.
[format]
auto = true # use the project's own tools (off by default)
timeout_secs = 15
[format.commands] # glob or extension = command; {file} is the path (else appended)
"*.rs" = "rustfmt --edition 2021"
"web/**/*.{ts,tsx}" = "npx prettier --write {file}"
[lint]
auto = true # ruff check (when ruff is configured), eslint (when in package.json)
timeout_secs = 60
[lint.commands]
py = "ruff check --quiet"Explicit commands are always used; the most specific pattern wins, and enabled = false turns the section off. With auto = true, Chanto picks a tool only when the project clearly uses it: rustfmt --edition <Cargo.toml's> for Rust with rustfmt installed, gofmt -w with a go.mod, ruff format when ruff is configured (ruff.toml or [tool.ruff]), else black when [tool.black] is, clang-format -i with a .clang-format, and prettier for JS/TS/CSS/JSON/Markdown/HTML/YAML when it's in package.json. Only files inside the project are touched. These are your commands, run like hooks: [format] and [lint] in a project config apply only once the folder is trusted, nothing runs under the read-only sandbox policy or when an editor (ACP) owns the file, and a background run's commands run in its container. They complement the done-gate (--verify), which still runs the project's checks at the end.
The path that {file} stands for is quoted for the shell that runs the command (single quotes for bash and PowerShell; under cmd.exe it becomes "%CHANTO_FILE%"), so $(…), backticks or %VAR% in a file name are never expanded. The path is also in the CHANTO_FILE environment variable. A format, lint or verify command is finished when its own process exits: output arriving within a short grace period after that is kept, and a daemon it left running in the background (holding the output open) no longer stalls the turn until the time limit.
Language servers
Servers start lazily (a file of that language is read or edited), one per language per project root (nearest Cargo.toml, package.json/tsconfig.json, pyproject.toml, go.mod, compile_commands.json, …). After Write/Edit/MultiEdit, Chanto waits briefly for diagnostics (2.5 s by default, 8 s for rust-analyzer while it indexes; usually well under a second once warm) and reports errors only, as
<diagnostics file="src/main.rs">
L4:10 error: cannot move out of `v` because it is borrowed [rustc E0505]
</diagnostics>[lsp]
# enabled = false # turn off language servers
# diagnostics_wait_ms = 2500
[lsp.servers.zls] # add a server (or override a built-in by name)
command = "zls"
extensions = ["zig"]Some servers run project code: rust-analyzer runs build scripts and proc macros, typescript-language-server loads the project's own TypeScript, gopls may download toolchains, and pylsp loads linter plugins. These only start in trusted folders. pyright and clangd run anywhere. Servers only start for files inside the project or an --add-dir directory, with at most 4 running at once. In a Cargo workspace, one rust-analyzer serves all member crates. [lsp] in a project config is ignored until the folder is trusted. Set CHANTO_LSP_DEBUG=1 to log server traffic to stderr.
Environment overrides: CHANTO_MODEL, CHANTO_BASE_URL, CHANTO_PROVIDER, CHANTO_API_KEY, CHANTO_HOME, BRAVE_API_KEY, TAVILY_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY.
Hosted models
kind | API | Default base_url | Key |
|---|---|---|---|
ollama | Ollama /api/chat | http://localhost:11434 | — |
openai | OpenAI-compatible /chat/completions (llama.cpp, LM Studio, vLLM, …) | http://localhost:8080/v1 | api_key |
anthropic | Anthropic Messages API | https://api.anthropic.com | api_key or ANTHROPIC_API_KEY |
openai-responses | OpenAI Responses API | https://api.openai.com/v1 | api_key or OPENAI_API_KEY |
bedrock, vertex, azure, foundry | Cloud platforms, see Cloud providers | from region, project or resource | the cloud's credentials |
A [providers.<name>] section without kind is ollama, unless its base_url ends in /v1: then it's openai. If the server answers in the other format (Server-Sent Events or choices to the Ollama provider, Ollama's NDJSON to the OpenAI one), the error says so and names the provider/kind to set.
Built-in providers need no [providers] section: chanto providers lists them (Anthropic, OpenAI, your ChatGPT plan, OpenRouter, Gemini, DeepSeek, xAI, Mistral, Groq, Cerebras, Together, Fireworks, Moonshot, Z.ai, MiniMax, DashScope, Hugging Face, NVIDIA, Vercel AI Gateway, OpenCode Zen, Baseten, Perplexity, Cohere, DeepInfra, Nebius, SambaNova, SiliconFlow, Upstage, AI21, Inception, Requesty, Venice, Chutes, Nano-GPT, Scaleway, DigitalOcean, Weights & Biases, OVHcloud, Crusoe, Morph, Poe, StepFun, Volcengine (Doubao), Xiaomi MiMo, Ollama Cloud, the coding plans, the cloud platforms and Chanto cloud) and whether you're logged in. Each works with its key variable (GEMINI_API_KEY, GROQ_API_KEY, …) or chanto login <provider>. With ANTHROPIC_API_KEY or OPENAI_API_KEY set, the provider names anthropic and openai work without any config. For example, chanto -m anthropic/claude-opus-5-5, subagent_model = "anthropic/claude-haiku-5-5" or /model openai/gpt-6.1-sol. To use one as the default provider, set provider = "anthropic" and model = "claude-opus-5-5".
For a ChatGPT plan, run chanto login chatgpt (or /login chatgpt), then /models to discover your account's available model names. Select an exact name with /model chatgpt/<model> or chanto -m chatgpt/<model>. Logging in does not select a model, and Chanto stores its credentials separately from Codex. Model access depends on your account; a model-access rejection is different from an expired or rejected login. OpenAI API-key access remains separate as openai/<model>. The chatgpt login uses Sign in with ChatGPT and draws from your existing Codex/ChatGPT Work allowance; a separate login to the same account does not add quota. Review connected-app usage and limits in ChatGPT Settings → Usage. See OpenAI’s explanation.
Subscription coding plans
Subscription coding plans whose vendors allow them in third-party coding tools are built in. Each takes the plan's own key, which is not the vendor's pay-as-you-go API key, through chanto login <provider> (or --as <name> for a second account) or its variable. Most speak the vendor's OpenAI-compatible endpoint (MiniMax's M Plan is Anthropic-compatible; see below), and their usage shows no per-token price. Anthropic's Claude Code subscription (Pro/Max) is intentionally absent: its terms don't permit use from other tools, so use the anthropic provider with an API key or Chanto cloud instead.
chanto login kimi-coding # paste a Kimi Code membership key
chanto login zai # paste a Z.ai GLM Coding Plan keyInside a session, the equivalents are /login kimi-coding and /login zai. Login saves credentials; select a model separately with /model kimi-coding/kimi-for-coding, or use /models after choosing the provider to discover its models. These logins use plan keys rather than a browser sign-in.
| Provider | Plan | Endpoint | Key | Models |
|---|---|---|---|---|
zai | Z.ai GLM Coding Plan | https://api.z.ai/api/coding/paas/v4 | ZAI_API_KEY | /models |
kimi-coding | Kimi Code membership | https://api.kimi.ai/coding/v1 | KIMI_CODE_API_KEY; create one in the Kimi Code console | kimi-for-coding (1M context), kimi-for-coding-highspeed, k3, k3-256k |
kimi-coding-cn | Kimi Code membership (China) | https://api.kimi.com/coding/v1 | KIMI_CODE_API_KEY | same as kimi-coding |
minimax-coding | MiniMax M Plan | https://api.minimax.io/v1 | MINIMAX_CODING_API_KEY; the sk-cp-… subscription key from the M Plan console | MiniMax-M3.1-Flash-Preview (1M context), MiniMax-M3, MiniMax-M2.7 |
qwen-coding | Alibaba Qwen Code plan | https://coding-intl.dashscope.aliyuncs.com/v1 | ALIBABA_CODING_PLAN_API_KEY | /models |
qwen-coding-cn | Alibaba Qwen Code plan (China) | https://coding.dashscope.aliyuncs.com/v1 | ALIBABA_CODING_PLAN_API_KEY | /models |
tencent-coding | Tencent CodeBuddy plan | https://api.lkeap.cloud.tencent.com/coding/v3 | TENCENT_CODING_PLAN_API_KEY | /models |
volcengine-coding | Volcengine (Doubao) coding plan | https://ark.cn-beijing.volces.com/api/coding/v3 | ARK_CODING_PLAN_API_KEY | /models |
stepfun-plan | StepFun Step plan | https://api.stepfun.ai/step_plan/v1 | STEPFUN_API_KEY | /models |
stepfun-plan-cn | StepFun Step plan (China) | https://api.stepfun.com/step_plan/v1 | STEPFUN_API_KEY | /models |
cline | Cline Pass | https://api.cline.bot/api/v1 | CLINE_API_KEY | /models |
For example, chanto login kimi-coding, then chanto -m kimi-coding/kimi-for-coding. /models lists what the plan's API reports, or the vendor-documented models above if it has no list. Kimi's k3 reaches a 1M window only on higher membership tiers, so Chanto budgets 256K for it. The -cn providers use the mainland-China endpoint with the same key, so pick whichever matches where your account was created. MiniMax's M Plan is Anthropic-compatible, so point [providers.minimax-coding] at https://api.minimax.cn/anthropic/v1 (China) or https://api.minimax.io/anthropic/v1 with kind = "anthropic" if your key only works there.
Usage and provider errors
/usage (alias /cost) reports session tokens, spend and any account limits reported by the provider. The footer shows the most-used reported window, its remaining percentage, a local reset time when available, and /usage when space permits:
5h limit: 27% left · resets at 10:09 PM · /usageOn a narrow terminal, reset details and the hint drop before the percentage. At 25% remaining, the quota turns amber and a one-time heads-up appears; at 10% remaining, it turns red and warns once more. Warnings re-arm after a reset. Weekly limits can take precedence when they are more exhausted than the 5-hour window. /usage lists all reported windows.
These are the provider’s last reported values, read from response headers or stream events; Chanto does not poll a separate quota endpoint or estimate missing percentages. If no limits have been reported, /usage says so. An elapsed reset timestamp displays reset due until a fresh report arrives; it does not imply that the allowance has already replenished. The separate ctx percentage is conversation-context usage, not your plan’s remaining allowance. Quota banners are for the user and are not automatically added to the model prompt. Limits and resets are enforced by the provider and depend on your plan.
Provider safety notices. Refusal text and links appear in the streamed reply and transcript when the provider supplies them. A safety-filter stop without an explanation shows a clear fallback notice. Filtered turns end without automatic retries, tool execution or continuation through verification/stop hooks. Provider-specific access-program wording is shown only when supplied; Chanto does not invent it.
Waiting out a usage limit. When a plan's 5-hour or weekly window runs out mid-turn (usage_limit_reached, with a reset time from the error, Retry-After, or the last reported limits), Chanto can keep the session and continue the same turn when the window resets instead of failing it. With resume_after_limit = true it always waits; when unset, the TUI asks "Wait and continue at 14:05?" (a reset on another day shows the day), and -p runs fail as before. While waiting, the status line shows ⏸ usage limit · resuming at 14:05 and the spinner counts down; esc stops waiting and ends the turn. When it resumes, Chanto sends a desktop notification (the notifications setting) and fires a Notification hook with notification_type: "limit_resumed". Billing quotas (insufficient_quota) don't reset on a clock, so they still fail right away, and a reset more than eight days off isn't waited for.
resume_after_limit = true # false: never wait; unset: ask in the TUISeveral accounts per provider. chanto login <provider> --as <name> saves another login for the same provider (a work and a personal ChatGPT plan, two API keys) beside the default one; --with-api-key and the browser logins take --as too, and chanto logout <provider> --as <name> removes one. account = "<name>" in the config (or a profile) uses that login for every provider that has one by that name, and the default login for the rest. In a session, /login lists saved logins with the one in use marked, /login <provider> <name> switches for the rest of the session (default goes back to the unnamed one), /login chatgpt --as <name> signs in as a new account and switches to it, and /status shows the account in use. Named logins are stored in ~/.chanto/auth.json as <provider>@<name> entries, so older Chanto versions keep reading the default ones. Listings never show keys or tokens. A project config can set account only once the folder is trusted.
Hosted provider behavior
How the hosted providers behave:
- Anthropic:
- Prompt caching is on, with breakpoints on the system prompt, the tool list and the latest user turn.
- Thinking is adaptive on current models and uses a thinking budget on older ones.
- Thinking blocks and their signatures are sent back unchanged with the turn they belong to.
- OpenAI Responses:
- Requests are stateless (
store: false). - Reasoning models get
reasoning.effortand return encrypted reasoning, which is passed back with the following turns.
- Requests are stateless (
- Both:
- Rate-limit and overload errors (429, 529, 5xx) are retried, honoring
retry-after. - The context budget defaults to the model's window (from Anthropic's Models API, or a per-model default for OpenAI) instead of
num_ctx.
- Rate-limit and overload errors (429, 529, 5xx) are retried, honoring
Cloud providers (Bedrock, Vertex AI, Azure)
Organizations that must use their cloud's model endpoints can select them like any other provider:
An AWS profile or gcloud login on your machine is not taken as a choice to use it: /model and chanto models list a cloud platform's models only once you opt in, with a key saved by chanto login <platform>, the platform's API-key variable (AWS_BEARER_TOKEN_BEDROCK, …), a [providers.<platform>] section, a default model on that platform, or CHANTO_USE_BEDROCK=1 / CHANTO_USE_VERTEX=1 / CHANTO_USE_AZURE=1 / CHANTO_USE_FOUNDRY=1 (Claude Code's CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX and CLAUDE_CODE_USE_FOUNDRY count too). chanto setup still offers them when it finds credentials, and chanto providers shows what it found.
| Spec | Platform | Wire API | Credentials |
|---|---|---|---|
bedrock/<model-id or inference profile> | Amazon Bedrock | Claude: InvokeModelWithResponseStream (Anthropic Messages). Others: ConverseStream | AWS credential chain (SigV4), or a Bedrock API key |
vertex/<model> | Google Vertex AI | Claude: streamRawPredict (Anthropic Messages). Gemini and Model Garden: OpenAI-compatible endpoint | Application Default Credentials |
azure/<deployment> | Azure OpenAI (v1 API) | chat completions, or Responses for codex and -pro deployments | API key or Microsoft Entra ID |
foundry/<model> | Azure AI Foundry | Claude: Anthropic Messages. Others: OpenAI-compatible chat completions | API key or Microsoft Entra ID |
Credentials are resolved on the first request to a cloud model, never at startup, and cached until five minutes before they expire. Claude on every cloud gets the same prompt caching, thinking and tool handling as the Anthropic API. The context budget for Claude on a cloud is 200k tokens. chanto doctor shows where credentials come from (never their values), the region, project or endpoint, and the result of a check that spends no tokens. chanto providers shows the same in one line per cloud. Setup suggests a cloud only when credentials and a region, project or endpoint are present, and ranks it after local models.
Optional settings, in ~/.chanto/config.toml. A section named after a platform needs no kind. A section with another name needs a kind (kind = "bedrock", for example) and can point at a separate account:
[providers.bedrock]
region = "us-east-1" # default: AWS_REGION, AWS_DEFAULT_REGION, then the profile's region
profile = "ml-dev" # default: AWS_PROFILE, then "default". If set, it wins over AWS_* key variables
# api = "converse" # force the Converse API (default: Anthropic Messages for Claude model IDs and ARNs that name one)
# base_url = "https://vpce-….bedrock-runtime.us-east-1.vpce.amazonaws.com" # PrivateLink endpoint
[providers.vertex]
project = "my-project" # default: GOOGLE_CLOUD_PROJECT, then the service account's or gcloud's project, then the ADC quota project
region = "us-east5" # default: CLOUD_ML_REGION, then "global"
[providers.azure]
resource = "my-openai" # https://my-openai.openai.azure.com. Default: AZURE_OPENAI_ENDPOINT
# api = "responses" # or "chat". Default: Responses for codex and -pro deployments only
# api_version = "preview" # appended as ?api-version=; the v1 API needs none
[providers.foundry]
resource = "my-foundry" # https://my-foundry.services.ai.azure.com. Default: AZURE_FOUNDRY_RESOURCECosts: Claude on Bedrock, Vertex AI and Foundry is priced like the Anthropic API, and Azure deployments named after an OpenAI model are priced like OpenAI. Regional-endpoint and data-zone premiums are not modeled. Common Bedrock models (gpt-oss, Nova, Llama 3.3, DeepSeek R1) and Vertex Gemini 2.5 models have prices. Other models show as unknown; add them under [pricing].
Amazon Bedrock
export AWS_PROFILE=ml-dev AWS_REGION=us-east-1 # or AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY[/AWS_SESSION_TOKEN]
chanto -m bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0
chanto -m bedrock/openai.gpt-oss-120b-1:0 # ConverseModel IDs. Newer models are invoked through an inference profile:
us.,eu.orapac.for a geography, orglobal.. Use one of these instead of the base model ID (anthropic.claude-…). Application inference profile ARNs also work. Setapi = "anthropic"when the ARN doesn't name a Claude model. Enable each model under Model access in the Bedrock console for the region./modelslists the profiles and on-demand models the account can call, if[providers.bedrock]is configured.Credentials, in order:
- A Bedrock API key:
AWS_BEARER_TOKEN_BEDROCK,chanto login bedrock, orapi_keyin the section. It is sent as a bearer token. - The profile named in Chanto config.
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_SESSION_TOKEN.- The
AWS_PROFILE(ordefault) profile in~/.aws/credentialsand~/.aws/config: static keys,credential_process, or an IAM Identity Center session fromaws sso login. - Container credentials (ECS/EKS) and EC2 instance profiles (IMDSv2).
Profiles that need anything else (assume-role chains, web identity) are resolved by running
aws configure export-credentials, which needs AWS CLI 2.9 or later.AWS_CONFIG_FILE,AWS_SHARED_CREDENTIALS_FILE,AWS_ENDPOINT_URL_BEDROCK_RUNTIMEandAWS_ENDPOINT_URL_BEDROCKare honored.- A Bedrock API key:
IAM permissions:
bedrock:InvokeModelWithResponseStreamon the foundation models and inference profiles you use. Cross-region profiles need it on the profile ARN and on the foundation model in each destination region. Optional:bedrock:ListInferenceProfilesandbedrock:ListFoundationModels, for/modelsand thechanto doctorcheck. Without them doctor reports a permission note, and requests still work.
{"Version": "2012-10-17", "Statement": [
{"Effect": "Allow", "Action": ["bedrock:InvokeModelWithResponseStream", "bedrock:InvokeModel"],
"Resource": ["arn:aws:bedrock:*::foundation-model/*", "arn:aws:bedrock:*:<account-id>:inference-profile/*"]},
{"Effect": "Allow", "Action": ["bedrock:ListFoundationModels", "bedrock:ListInferenceProfiles"], "Resource": "*"}]}Google Vertex AI
gcloud auth application-default login # or GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json
export GOOGLE_CLOUD_PROJECT=my-project CLOUD_ML_REGION=global
chanto -m vertex/claude-sonnet-4-5@20250929
chanto -m vertex/gemini-2.5-pro # sent as google/gemini-2.5-pro
chanto -m vertex/meta/llama-4-maverick-17b-128e-instruct-maas- Model names. Claude models use Vertex's names (
claude-sonnet-4-5@20250929) and must be enabled in Model Garden for the project. A bare Gemini name gets thegoogle/publisher prefix. Other Model Garden models are given aspublisher/model. Forglobal, requests go toaiplatform.googleapis.com. For any other region, they go to<region>-aiplatform.googleapis.com. Claude is available inglobaland in specific regions such asus-east5andeurope-west1. - Credentials: Application Default Credentials. User credentials (
authorized_user) are refreshed with their refresh token. Service account keys sign an RS256 JWT locally. Other types (workload identity federation, impersonation) and machines without an ADC file rungcloud auth application-default print-access-tokenorgcloud auth print-access-token.chanto login vertexdoesn't apply, since there are no keys. - IAM:
roles/aiplatform.user(Vertex AI User) on the project, which includesaiplatform.endpoints.predict. The project needs the Vertex AI API (aiplatform.googleapis.com) enabled. The doctor check reads the project's location (GET …/projects/<project>/locations/<region>). If it reports a permission note while requests work, the role is narrower than Vertex AI User.
Azure OpenAI and Azure AI Foundry
export AZURE_OPENAI_ENDPOINT=https://my-openai.openai.azure.com AZURE_OPENAI_API_KEY=…
chanto -m azure/my-gpt-5-deployment # the deployment name, not the model name
export AZURE_FOUNDRY_RESOURCE=my-foundry AZURE_FOUNDRY_API_KEY=…
chanto -m foundry/claude-sonnet-4-5 # the deployment name in Foundry- Azure OpenAI uses the v1 API (
https://<resource>.openai.azure.com/openai/v1/) with theapi-keyheader.AZURE_OPENAI_DEPLOYMENTadds your deployment to/model. Requests sendmax_completion_tokens, and optional parameters a deployment rejects (such astemperatureon reasoning models) are dropped and the request is retried. - Foundry sends Claude to
https://<resource>.services.ai.azure.com/anthropic/v1/messageswithx-api-key. Other models go to…/openai/v1/chat/completions.ANTHROPIC_FOUNDRY_RESOURCE,ANTHROPIC_FOUNDRY_API_KEYandANTHROPIC_FOUNDRY_BASE_URLare accepted too. - Without an API key, Chanto uses a Microsoft Entra ID token for
https://cognitiveservices.azure.com. It comes from a service principal (AZURE_TENANT_ID,AZURE_CLIENT_ID,AZURE_CLIENT_SECRET), or else from the Azure CLI (az login, thenaz account get-access-token). The Azure CLI is optional and only needed for this path. - Roles: with Entra ID, the identity needs Cognitive Services OpenAI User (Azure OpenAI) or Azure AI User (Foundry) on the resource. With an API key, no role is needed.
Live smoke test
The test suite never contacts a cloud and never reads your real credentials. An opt-in test makes one small real request per configured cloud, using your environment:
CHANTO_LIVE_CLOUD=1 CHANTO_LIVE_BEDROCK_MODEL=us.anthropic.claude-haiku-4-5-20251001-v1:0 \
CHANTO_LIVE_VERTEX_MODEL=gemini-2.5-flash CHANTO_LIVE_AZURE_MODEL=<deployment> CHANTO_LIVE_FOUNDRY_MODEL=<deployment> \
cargo test live_cloud_smoke -- --nocapture