Files
NetMesh/docs/research/claude-code-ollama-cloud-automation.md
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

13 KiB
Raw Blame History

Claude Code + Ollama Cloud 替换 Cursor 自动化 — 可行性调研

Date: 2026-08-27 Status: feasible for triage; keep the existing control plane Scope: replace Cursor CLI as the agent runner, not rewrite GitHub routing

Why this exists

The current pipeline is .github/workflows/ai-automation.yml plus scripts/ai-automation.cjs. Live mode is triage_only: classify issues, do not implement, do not run the Codex fix loop.

A production classify run failed because Cursor hit its usage limit:

ActionRequiredError: You've hit your usage limit Get Cursor Pro for more Agent usage

Example: https://github.com/binaricat/Netcatty/actions/runs/33050247387 (job Classify issue, step Research external context for classification).

The goal is to keep issue triage running by swapping the agent from Cursor CLI to Claude Code, with Ollama Cloud as the Anthropic-compatible backend. Secrets stay in GitHub Actions (set via gh), never in the repo.

Verdict

Yes, this is feasible. Do not replace the workflow with anthropics/claude-code-action. Keep the existing router, labels, rate limits, bot identity, isolated research workspace, and publish steps. Only replace the agent invocation.

Layer Keep or replace Why
Event routing, labels, daily limits, handoff comments Keep Pure GitHub control plane in ai-automation.cjs
Codex @codex review loop Keep Independent of Cursor; currently paused by triage_only
Isolated research workspace + imgproxy screenshots Keep Safety contract is still needed
Frozen helper copy, leak scan, no GITHUB_TOKEN in agent Keep Same threat model
Cursor CLI install, AppArmor sandbox, API-key fd bridge Replace This is the part that is out of credits
agent -p --sandbox enabled Replace claude --bare -p with dontAsk + allowlist

Current production path that must come back first: research + classify. Implement / follow-up / Codex-fix jobs can stay paused until triage is green.

How the current pipeline actually works

The YAML is large because it is an orchestrator, not “run an agent on the issue”. Jobs:

  1. route — decide issue_classify / issue_followup / codex_loop / skip
  2. classify — prepare issue JSON, isolated web research, classify, apply labels
  3. implement / followup / codex_loop — gated off in triage_only
  4. Codex re-request and source-issue cleanup — GitHub-only, no Cursor

Classify currently shells out to Cursor like this:

sudo --preserve-env=HOME,RUNNER_TEMP,GITHUB_WORKSPACE \
  "$RUNNER_TEMP/ai-claude-authenticated" \
  -p --mode=ask --trust --sandbox enabled --model auto --output-format text \
  --workspace "$GITHUB_WORKSPACE" \
  "$PROMPT"

Research uses the same binary in an empty temp workspace, --output-format stream-json, then parseExternalResearchStream() requires a real completed WebSearch/WebFetch tool event. Classify must write .ai-runtime/classification.json with category, confidence, summary, reasoning, reply, code_paths, code_findings.

That JSON contract is owned by normalizeClassification() in scripts/ai-automation.cjs, not by the checked-in .github/ai/schemas/classification.schema.json (the schema file is stale: it omits already_available, code_paths, and code_findings).

Claude Code as a headless runner

Official headless mode is claude -p (docs):

  • Exit 0 / non-zero for scripts
  • --bare skips hooks, skills, plugins, MCP, CLAUDE.md (recommended for CI)
  • --output-format json or stream-json
  • --json-schema can enforce the classification object
  • --permission-mode dontAsk is the documented “locked-down CI” mode: only pre-allowed tools run; everything else is denied, never prompted
  • --allowedTools / --disallowedTools for the allowlist
  • Auth in -p / --bare is ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN, not a Claude.ai subscription login

Do not use --dangerously-skip-permissions for classify. Classify is read-only. dontAsk plus Read/Grep/Glob (and maybe Bash(rg *)) is enough.

Do not adopt anthropics/claude-code-action as the new workflow. That action is built for @claude mentions, the Claude GitHub App, and posting its own comments. This repo already has netcatty-bot, admission quotas, untrusted-issue sanitization, and a separate research pass. The official action would fight that control plane. Invoke the CLI the same way Cursor is invoked today.

Ollama Cloud as the Anthropic endpoint

Ollama documents an Anthropic Messages compatibility layer, including tools, streaming, vision, and thinking (docs).

Two ways to reach cloud models:

  1. Local Ollama proxying :cloud modelsANTHROPIC_BASE_URL=http://localhost:11434. Needs a local Ollama daemon and sign-in. Wrong for GitHub-hosted runners.
  2. Direct ollama.com APIANTHROPIC_BASE_URL=https://ollama.com plus an Ollama Cloud API key. This is the CI path.

Ollama Clouds native auth is Authorization: Bearer $OLLAMA_API_KEY against https://ollama.com. Claude Code maps:

Claude Code env HTTP header Use with Ollama Cloud
ANTHROPIC_BASE_URL API host https://ollama.com
ANTHROPIC_AUTH_TOKEN Authorization: Bearer … the Ollama Cloud key
ANTHROPIC_API_KEY X-Api-Key leave empty, or same key if a probe requires it

A user-reported Ollama docs bug (#13854) says setting ANTHROPIC_API_KEY alone is not enough for cloud; the Bearer token (ANTHROPIC_AUTH_TOKEN) plus https://ollama.com is.

Must pin the model. Claude Code defaults to Anthropic IDs such as claude-sonnet-4-6. Ollama Cloud will 404 those. Also set the Haiku/Sonnet/Opus alias env vars so background/compaction calls do not fall back to Claude names:

ANTHROPIC_MODEL=<ollama-cloud-id>
ANTHROPIC_DEFAULT_HAIKU_MODEL=<same or cheaper cloud id>
ANTHROPIC_DEFAULT_SONNET_MODEL=<same>
ANTHROPIC_DEFAULT_OPUS_MODEL=<same>

Ollamas Claude Code page recommends coding cloud models such as glm-4.7:cloud, minimax-m2.1:cloud, and shows kimi-k2.7-code:cloud in the manual example. Pick one coding model and keep it in a repo variable, not hardcoded in YAML, so it can change without a workflow PR.

Ollama does not support prompt caching, /v1/messages/count_tokens, or forcing tool_choice. Fine for classify. Token counts are approximations.

Web research gap (the one real risk)

Cursor research depends on Cursors built-in WebSearch/WebFetch, then the helper asserts a completed web-tool event in the stream.

Claude Codes WebSearch/WebFetch are Anthropic server-side tools. They are not guaranteed to work when ANTHROPIC_BASE_URL is https://ollama.com. Ollama has its own REST APIs instead:

  • POST https://ollama.com/api/web_search
  • POST https://ollama.com/api/web_fetch

Those use the same Cloud API key. ollama launch claude wires them for local use; a raw claude -p on a GHA runner may not.

Recommended research strategy for this repo:

  1. Keep the empty-temp-workspace + imgproxy design.
  2. Prefer a control-plane research helper that calls Ollama web_search / web_fetch (or Claude Code WebSearch if a smoke test proves it works against ollama.com).
  3. Rewrite parseExternalResearchStream() to accept Claude stream-json or a small JSONL log from the control-plane helper. Do not drop the “must have a real source URL” check.
  4. Classify still runs with WebSearch/WebFetch denied.

If research cannot get a source for a needed external fact, keep todays behavior: RESEARCH_BLOCKED → maintainer handoff. Do not invent sources.

Security mapping

Keep these invariants from .github/ai/README.md:

  • Agent steps get no GitHub token
  • Automation never publishes .github/ or automation scripts
  • Issue text is sanitized before prompts
  • Output is scanned for the provider secret
  • External research has no repo checkout and no GitHub credentials

Cursor-specific pieces we will not copy 1:1:

  • AppArmor profile from downloads.cursor.com
  • fd-preload that injects --api-key without putting it in env/argv

Claude Code requires ANTHROPIC_AUTH_TOKEN in the process environment. Mitigations:

  • Stage the key the same way (install -m 0400 into RUNNER_TEMP), export only on the claude invocation, never as a job-wide env:
  • --bare so project .mcp.json / hooks cannot run
  • --permission-mode dontAsk + deny WebSearch / WebFetch / Edit / Write on classify
  • Claude sandbox (sandbox.enabled) if the GHA image can load it; if not, fail closed for implement later, but classify can ship on dontAsk + no GitHub token
  • Continue leak scans against ANTHROPIC_AUTH_TOKEN

Do not run claude as root/sudo. Cursors launcher used setpriv to drop privileges after reading the key. Claude Code refuses --dangerously-skip-permissions as root; classify should run as the runner user.

What to change (phased)

Phase 0 — secrets, no YAML behavior change

Set via gh against binaricat/Netcatty. Values never land in git.

# Key: GitHub secret (Bearer token for ollama.com)
printf '%s' "$OLLAMA_API_KEY" | gh secret set ANTHROPIC_AUTH_TOKEN -R binaricat/Netcatty

# Host: repo variable is enough (not a credential)
gh variable set ANTHROPIC_BASE_URL -R binaricat/Netcatty --body 'https://ollama.com'

# Model: repo variable so it can change without rotating the key
gh variable set CLAUDE_CODE_MODEL -R binaricat/Netcatty --body 'kimi-k2.7-code:cloud'

Optional aliases, same key:

gh variable set ANTHROPIC_DEFAULT_HAIKU_MODEL  -R binaricat/Netcatty --body 'kimi-k2.7-code:cloud'
gh variable set ANTHROPIC_DEFAULT_SONNET_MODEL -R binaricat/Netcatty --body 'kimi-k2.7-code:cloud'
gh variable set ANTHROPIC_DEFAULT_OPUS_MODEL   -R binaricat/Netcatty --body 'kimi-k2.7-code:cloud'

Do not commit the key, put it in .github/workflows/*.yml literals, or paste it into issues/PRs.

Existing ANTHROPIC_AUTH_TOKEN can stay until Cursor jobs are deleted.

Phase 1 — restore triage (this unblocks production)

In .github/workflows/ai-automation.yml classify job:

  1. Install Claude Code: curl -fsSL https://claude.ai/install.sh | bash
  2. Drop Cursor CLI install, credential bridge, AppArmor sandbox host
  3. Research step: isolated workspace + Ollama web APIs or proven Claude WebSearch; parse a Claude/control-plane research envelope
  4. Classify step:
claude --bare -p "$PROMPT" \
  --permission-mode dontAsk \
  --allowedTools "Read,Grep,Glob" \
  --disallowedTools "WebSearch,WebFetch,Edit,Write,NotebookEdit" \
  --output-format json \
  --json-schema "$(cat "$RUNNER_TEMP/classification.schema.json")" \
  --model "$CLAUDE_CODE_MODEL"
  1. Point prompts at .ai-runtime/issue.json still (runtime path rename can wait)
  2. Update parseClassificationFile to read Claude --output-format json (structured_output or result) in addition to raw JSON files
  3. Keep applyClassification, daily limits, Slack, failure handoff
  4. Replace sandbox_smoke with a cheap authenticated claude -p ping against Ollama Cloud

Also refresh .github/ai/schemas/classification.schema.json so it matches CATEGORIES + code_paths / code_findings. Use that schema with --json-schema.

Helper script: add prepareClaudeCliSettings() next to prepareAiCliSettings(). Do not rename ai-automation.cjs in this phase (issue-format, markers, tests all import that path).

Phase 2 — implement / follow-up (only after triage is stable)

Same CLI, wider allowlist (Edit, Write, Bash for tests), still no GitHub token, still deny .github/ publishes. Restore AI_AUTOMATION_MODE=full only after classify quality looks acceptable on real issues.

Out of scope unless asked

  • Installing the Claude GitHub App / @claude comments
  • Switching review from Codex to Claude
  • Running a local Ollama daemon on GHA
  • Paying Anthropic first-party API (the point of Ollama Cloud)

Suggested classify invocation contract

Env on the agent step only:

ANTHROPIC_BASE_URL: ${{ vars.ANTHROPIC_BASE_URL }}
ANTHROPIC_AUTH_TOKEN: (staged file, not a job-wide secret expansion in logs)
ANTHROPIC_API_KEY: ''
ANTHROPIC_MODEL: ${{ vars.CLAUDE_CODE_MODEL }}
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1'
GITHUB_TOKEN: ''
GH_TOKEN: ''

Prompt files stay under .github/ai/prompts/ for now. Swap the one Cursor-specific sentence in research.md (“Use only Cursor's built-in WebSearch and WebFetch tools”) to the new research tools.

Sources