vet

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit c6ef691668aae9c05a516df98c5770a1aba98717
parent d5477ef52ff87cf1a7a65c4c00057bc4dab0301a
Author: andrewlaack-collab <andrew.laack@imbue.com>
Date:   Wed, 18 Feb 2026 20:47:48 +0000

Make Claude Code and Codex work without API keys (#97)

* Fixing claude code without api key

* Remove bypassPermissions from Claude Code options

Claude Code rejects --dangerously-skip-permissions when running as root,
which is common in Docker containers. The --print flag (always passed)
already handles non-interactive mode, so the explicit permission bypass
is unnecessary.

* Fix

* Disable LLM filtration and deduplication in agentic mode

These post-processing stages use direct Anthropic API calls which
require an API key. In agentic mode, only the Claude Code CLI is
available, so skip these stages.

* Black

* Expand comment on why filtration/dedup are disabled in agentic mode

* Added message about agentic.

* Restore bypassPermissions for Claude Code options

* Don't run as root; that's not supported yet

* Use AgentUnknownBlock instead of None for unknown content blocks

Adds a typed AgentUnknownBlock that preserves raw data for debugging,
avoiding None propagation. Existing isinstance checks for known block
types naturally skip unknown blocks.

* Better case handling.

* Route issue filtration through agent harness in agentic mode

When --agentic is set, filtration now uses the same agent harness
(Claude Code CLI or Codex CLI) instead of requiring direct API calls.
This allows filtration to work without an API key while preserving
issue quality filtering.

- Add use_agent_harness_for_evaluation field to VetConfig
- Split evaluate_code_issue_through_llm into _evaluate_through_api
  and _evaluate_through_agent paths
- Re-enable filter_issues_through_llm_evaluator for agentic mode

* Support filtration

* Support filtration

* Revert agent-based filtration; disable filtration/dedup in agentic mode

Reverts the agent harness routing for filtration. Instead, simply
disable LLM filtration and deduplication in agentic mode since they
require direct API calls. The agentic identifier is more thorough
and these could be routed through the CLI in the future if needed.

* Clean up comments and docstrings in agentic mode changes

* Add AgentUnknownMessage for unhandled message types in Claude parser

* Reword --agentic description in skill docs

* Remove 'fallback' wording from --agentic description

---------

Co-authored-by: Andrew Laack <andrew@laack.co>
Diffstat:
Mskills/vet/SKILL.md | 1+
Muv.lock | 2+-
Mvet/cli/main.py | 5+++++
Mvet/imbue_core/agents/agent_api/claude/client.py | 3+--
Mvet/imbue_core/agents/agent_api/claude/message_parser.py | 13+++++++------
Mvet/imbue_core/agents/agent_api/data_types.py | 16+++++++++++++---
Mvet/issue_identifiers/common.py | 2+-
7 files changed, 29 insertions(+), 13 deletions(-)

diff --git a/skills/vet/SKILL.md b/skills/vet/SKILL.md @@ -74,4 +74,5 @@ Vet analyzes the full git diff from the base commit. This may include changes fr - `--confidence-threshold N`: Minimum confidence 0.0-1.0 (default: 0.8) - `--output-format FORMAT`: Output as `text`, `json`, or `github` - `--quiet`: Suppress progress output +- `--agentic`: Mode that routes analysis through the locally installed Claude Code or Codex CLI instead of calling the API directly. Try this if vet fails due to missing API keys. Slower (~3 min) so not recommended as the default. - `--help`: Show comprehensive list of options diff --git a/uv.lock b/uv.lock @@ -1511,7 +1511,7 @@ wheels = [ [[package]] name = "verify-everything" -version = "0.1.7" +version = "0.1.8" source = { editable = "." } dependencies = [ { name = "anthropic" }, diff --git a/vet/cli/main.py b/vet/cli/main.py @@ -508,9 +508,11 @@ def main(argv: list[str] | None = None) -> int: language_model_config = build_language_model_config(model_id, user_config) max_output_tokens = get_max_output_tokens_for_model(model_id, user_config) + enabled_identifiers = ("agentic_issue_identifier",) if args.agentic else None disabled_identifiers = None if args.agentic else ("agentic_issue_identifier",) config = VetConfig( + enabled_identifiers=enabled_identifiers, disabled_identifiers=disabled_identifiers, language_model_generation_config=language_model_config, enabled_issue_codes=(tuple(args.enabled_issue_codes) if args.enabled_issue_codes else None), @@ -522,6 +524,9 @@ def main(argv: list[str] | None = None) -> int: max_identifier_spend_dollars=args.max_spend, custom_guides_config=custom_guides_config, agent_harness_type=args.agent_harness, + # TODO: Evaluate if routing filtration/dedup through the agent harness is worth the tradeoff. + filter_issues_through_llm_evaluator=not args.agentic, + enable_deduplication=not args.agentic, ) try: diff --git a/vet/imbue_core/agents/agent_api/claude/client.py b/vet/imbue_core/agents/agent_api/claude/client.py @@ -79,8 +79,7 @@ class ClaudeCodeClient(RealAgentClient[ClaudeCodeOptions]): ) message = parse_claude_message(data) - if message: - yield message + yield message if isinstance(message, AgentResultMessage): break diff --git a/vet/imbue_core/agents/agent_api/claude/message_parser.py b/vet/imbue_core/agents/agent_api/claude/message_parser.py @@ -1,5 +1,4 @@ from typing import Any -from typing import assert_never from vet.imbue_core.agents.agent_api.data_types import AgentAssistantMessage from vet.imbue_core.agents.agent_api.data_types import AgentContentBlock @@ -11,11 +10,13 @@ from vet.imbue_core.agents.agent_api.data_types import AgentTextBlock from vet.imbue_core.agents.agent_api.data_types import AgentThinkingBlock from vet.imbue_core.agents.agent_api.data_types import AgentToolResultBlock from vet.imbue_core.agents.agent_api.data_types import AgentToolUseBlock +from vet.imbue_core.agents.agent_api.data_types import AgentUnknownBlock +from vet.imbue_core.agents.agent_api.data_types import AgentUnknownMessage from vet.imbue_core.agents.agent_api.data_types import AgentUsage from vet.imbue_core.agents.agent_api.data_types import AgentUserMessage -def parse_claude_message(data: dict[str, Any]) -> AgentMessage | None: +def parse_claude_message(data: dict[str, Any]) -> AgentMessage: """Parse message from CLI output using unified types. Reference: @@ -68,8 +69,8 @@ def parse_claude_message(data: dict[str, Any]) -> AgentMessage | None: original_message=data, ) - case _ as unreachable: - assert_never(unreachable) + case _: + return AgentUnknownMessage(raw=data, original_message=data) def parse_claude_system_event_type(subtype: str) -> AgentSystemEventType: @@ -113,5 +114,5 @@ def parse_claude_content_block(block: dict[str, Any]) -> AgentContentBlock: is_error=block.get("is_error"), ) - case _ as unreachable: - assert_never(unreachable) + case _: + return AgentUnknownBlock(raw=block) diff --git a/vet/imbue_core/agents/agent_api/data_types.py b/vet/imbue_core/agents/agent_api/data_types.py @@ -130,7 +130,11 @@ class AgentToolResultBlock(SerializableModel): exit_code: int | None = Field(default=None, description="Exit code for command executions") -AgentContentBlock = AgentTextBlock | AgentThinkingBlock | AgentToolUseBlock | AgentToolResultBlock +class AgentUnknownBlock(SerializableModel): + raw: dict[str, Any] + + +AgentContentBlock = AgentTextBlock | AgentThinkingBlock | AgentToolUseBlock | AgentToolResultBlock | AgentUnknownBlock class AgentSystemEventType(enum.StrEnum): @@ -219,12 +223,18 @@ class AgentResultMessage(SerializableModel): original_message: dict[str, Any] | None = Field(default=None, description="Original agent-specific message data") -AgentMessage = AgentUserMessage | AgentAssistantMessage | AgentSystemMessage | AgentResultMessage +class AgentUnknownMessage(SerializableModel): + raw: dict[str, Any] + original_message: dict[str, Any] | None = Field(default=None, description="Original agent-specific message data") + + +AgentMessage = AgentUserMessage | AgentAssistantMessage | AgentSystemMessage | AgentResultMessage | AgentUnknownMessage AgentMessageUnion = Annotated[ Annotated[AgentUserMessage, Tag("AgentUserMessage")] | Annotated[AgentAssistantMessage, Tag("AgentAssistantMessage")] | Annotated[AgentSystemMessage, Tag("AgentSystemMessage")] - | Annotated[AgentResultMessage, Tag("AgentResultMessage")], + | Annotated[AgentResultMessage, Tag("AgentResultMessage")] + | Annotated[AgentUnknownMessage, Tag("AgentUnknownMessage")], build_discriminator(), ] diff --git a/vet/issue_identifiers/common.py b/vet/issue_identifiers/common.py @@ -232,7 +232,7 @@ def get_agent_options(cwd: Path | None, model_name: str, agent_harness_type: Age model_name = _DEFAULT_CLAUDE_MODEL return ClaudeCodeOptions( cwd=cwd, - permission_mode="bypassPermissions", # Equivalent to --dangerously-skip-permissions + permission_mode="bypassPermissions", allowed_tools=list(READ_ONLY_TOOLS) + [AgentToolName.BASH], model=model_name, )