
Claude Code Clawdbot
- 21 installs
- 123 repo stars
- Updated February 1, 2026
- win4r/claude-code-clawdbot-skill
Helps with ai & agent building tasks.
About
claude-code-clawdbot is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- claude-code-clawdbot
- AI & Agent Building
- AI-coding skill
Claude Code Clawdbot by the numbers
- 21 all-time installs (skills.sh)
- Ranked #10,307 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 27, 2026 (Skillselion catalog sync)
npx skills add https://github.com/win4r/claude-code-clawdbot-skill --skill claude-code-clawdbotAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 21 |
|---|---|
| repo stars | ★ 123 |
| Last updated | February 1, 2026 |
| Repository | win4r/claude-code-clawdbot-skill ↗ |
What it does
Helps with ai & agent building tasks.
Files
Claude Code (Clawdbot)
Use the locally installed Claude Code CLI reliably.
This skill supports two execution styles:
- Headless mode (non-interactive): best for normal prompts and structured output.
- Interactive mode (tmux): required for slash commands like
/speckit.*(Spec Kit), which can hang or be killed when run via headless-p.
This skill is for driving the Claude Code CLI, not the Claude API directly.
Quick checks
Verify installation:
claude --versionRun a minimal headless prompt (prints a single response):
./scripts/claude_code_run.py -p "Return only the single word OK."Core workflow
1) Run a headless prompt in a repo
cd /path/to/repo
/home/ubuntu/clawd/skills/claude-code-clawdbot/scripts/claude_code_run.py \
-p "Summarize this project and point me to the key modules." \
--permission-mode plan2) Allow tools (auto-approve)
Claude Code supports tool allowlists via --allowedTools. Example: allow read/edit + bash:
./scripts/claude_code_run.py \
-p "Run the test suite and fix any failures." \
--allowedTools "Bash,Read,Edit"3) Get structured output
./scripts/claude_code_run.py \
-p "Summarize this repo in 5 bullets." \
--output-format json4) Add extra system instructions
./scripts/claude_code_run.py \
-p "Review the staged diff for security issues." \
--append-system-prompt "You are a security engineer. Be strict." \
--allowedTools "Bash(git diff *),Bash(git status *),Read"Notes (important)
- After correcting Claude Code's mistakes: Always instruct Claude Code to run:
"Update your CLAUDE.md so you don't make that mistake again."
This ensures Claude Code records lessons learned and avoids repeating the same errors.
- Claude Code sometimes expects a TTY.
- Headless: this wrapper uses
script(1)to force a pseudo-terminal. - Slash commands (e.g.
/speckit.*) are best run in interactive mode; this wrapper can start an interactive Claude Code session in tmux. - Use
--permission-mode planwhen you want read-only planning. - Keep
--allowedToolsnarrow (principle of least privilege), especially in automation.
High‑leverage Claude Code tips (from the official docs)
1) Always give Claude a way to verify (tests/build/screenshots)
Claude performs dramatically better when it can verify its work. Make verification explicit in the prompt, e.g.:
- “Fix the bug and run tests. Done when
npm testpasses.” - “Implement UI change, take a screenshot and compare to this reference.”
2) Explore → Plan → Implement (use Plan Mode)
For multi-step work, start in plan mode to do safe, read-only analysis:
./scripts/claude_code_run.py -p "Analyze and propose a plan" --permission-mode planThen switch to execution (acceptEdits) once the plan is approved.
3) Manage context aggressively: /clear and /compact
Long, mixed-topic sessions degrade quality.
- Use
/clearbetween unrelated tasks. - Use
/compact Focus on <X>when nearing limits to preserve the right details.
4) Rewind aggressively: /rewind (checkpoints)
Claude checkpoints before changes. If an approach is wrong, use /rewind (or Esc Esc) to restore:
- conversation only
- code only
- both
This enables “try something risky → rewind if wrong” loops.
5) Prefer CLAUDE.md for durable rules; keep it short
Best practice is a concise CLAUDE.md (global or per-project) for:
- build/test commands Claude should use
- repo etiquette / style rules that differ from defaults
- non-obvious environment quirks
Overlong CLAUDE.md files get ignored.
6) Permissions: deny > ask > allow (and scope matters)
In .claude/settings.json / ~/.claude/settings.json, rules match in order: deny first, then ask, then allow. Use deny rules to block secrets (e.g. .env, secrets/**).
7) Bash env vars don’t persist; use CLAUDE_ENV_FILE for persistence
Each Bash tool call runs in a fresh shell; export FOO=bar won’t persist. If you need persistent env setup, set (before starting Claude Code):
export CLAUDE_ENV_FILE=/path/to/env-setup.shClaude will source it before each Bash command.
8) Hooks beat “please remember” instructions
Use hooks to enforce deterministic actions (format-on-edit, block writes to sensitive dirs, etc.) when you need guarantees.
9) Use subagents for heavy investigation / independent review
Subagents can read many files without polluting the main context. Use them for broad codebase research or post-implementation review.
10) Treat Claude as a Unix utility (headless, pipes, structured output)
Examples:
cat build-error.txt | claude -p "Explain root cause"
claude -p "List endpoints" --output-format jsonThis is ideal for CI and automation.
Interactive mode (tmux)
If your prompt contains lines starting with / (slash commands), the wrapper defaults to auto → interactive.
Example:
./scripts/claude_code_run.py \
--mode auto \
--permission-mode acceptEdits \
--allowedTools "Bash,Read,Edit,Write" \
-p $'/speckit.constitution ...\n/speckit.specify ...\n/speckit.plan ...\n/speckit.tasks\n/speckit.implement'It will print tmux attach/capture commands so you can monitor progress.
Spec Kit end-to-end workflow (tips that prevent hangs)
When you want Claude Code to drive Spec Kit end-to-end via /speckit.*, do not use headless -p for the whole flow. Use interactive tmux mode because:
- Spec Kit runs multiple steps (Bash + file writes + git) and may pause for confirmations.
- Headless runs can appear idle and be killed (SIGKILL) by supervisors.
Prerequisites (important)
1) Initialize Spec Kit (once per repo)
specify init . --ai claude2) Ensure the folder is a real git repo (Spec Kit uses git branches/scripts):
git init
git add -A
git commit -m "chore: init"3) Recommended: set an origin remote (can be a local bare repo) so git fetch --all --prune won’t behave oddly:
git init --bare ../origin.git
git remote add origin ../origin.git
git push -u origin main || git push -u origin master4) Give Claude Code enough tool permissions for the workflow:
- Spec creation/tasks/implement need file writes, so include Write.
- Implementation often needs Bash.
Recommended:
--permission-mode acceptEdits --allowedTools "Bash,Read,Edit,Write"Run the full Spec Kit pipeline
./scripts/claude_code_run.py \
--mode interactive \
--tmux-session cc-speckit \
--permission-mode acceptEdits \
--allowedTools "Bash,Read,Edit,Write" \
-p $'/speckit.constitution Create project principles for quality, accessibility, and security.\n/speckit.specify <your feature description>\n/speckit.plan I am building with <your stack/constraints>\n/speckit.tasks\n/speckit.implement'Monitoring / interacting
The wrapper prints commands like:
tmux ... attach -t <session>to watch in real timetmux ... capture-pane ...to snapshot output
If Claude Code asks a question mid-run (e.g., “Proceed?”), attach and answer.
Operational gotchas (learned in practice)
1) Vite + ngrok: "Blocked request. This host (...) is not allowed"
If you expose a Vite dev server through ngrok, Vite will block unknown Host headers unless configured.
- Vite 7 expects
server.allowedHoststo betrueorstring[]. - ✅ Allow all hosts (quick):
server: { host: true, allowedHosts: true }- ✅ Allow just your ngrok host (safer):
server: { host: true, allowedHosts: ['xxxx.ngrok-free.app'] }- ❌ Do not set
allowedHosts: 'all'(won't work in Vite 7).
After changing vite.config.*, restart the dev server.
2) Don’t accidentally let your shell eat your prompt
When you drive tmux via a shell command (e.g. tmux send-keys ...), avoid unescaped backticks and shell substitutions in the text you pass. They can be interpreted by your shell before the text even reaches Claude Code.
Practical rule:
- Prefer sending prompts from a file, or ensure the wrapper/script quotes prompt text safely.
3) Long-running dev servers should run in a persistent session
In automation environments, backgrounded vite / ngrok processes can get SIGKILL. Prefer running them in a managed background session (Clawdbot exec background) or tmux, and explicitly stop them when done.
OpenSpec workflow (opsx)
OpenSpec is another spec-driven workflow (like Spec Kit) powered by slash commands (e.g. /opsx:*). In practice it has the same reliability constraints:
- Prefer interactive tmux mode for
/opsx:*commands (avoid headless-pfor the whole flow).
Setup (per machine)
Install CLI:
npm install -g @fission-ai/openspec@latestSetup (per project)
Initialize OpenSpec with tool selection (required):
openspec init --tools claudeTip: disable telemetry if desired:
export OPENSPEC_TELEMETRY=0Recommended end-to-end command sequence
Inside Claude Code (interactive): 1) /opsx:onboard 2) /opsx:new <change-name> 3) /opsx:ff (fast-forward: generates proposal/design/specs/tasks) 4) /opsx:apply (implements tasks) 5) /opsx:archive (optional: archive finished change)
If the UI prompts you for project type/stack, answer explicitly (e.g. “Web app (HTML/JS) with localStorage”).
Bundled script
scripts/claude_code_run.py: wrapper that runs the localclaudebinary with a pseudo-terminal and forwards flags.
# Secrets
.env
.env.*
# OS
.DS_Store
# Python
__pycache__/
*.pyc
# Logs
*.log
claude-code-clawdbot-skill
A Clawdbot skill to run Claude Code (Anthropic) on the host via the claude CLI (Agent SDK).
This repo provides:
SKILL.md: how Clawdbot should use this skillscripts/claude_code_run.py: a small wrapper that runsclaude -p ...through a pseudo-terminal to avoid non-TTY hangs
Why a wrapper?
Claude Code can behave differently when there is no TTY. In automation (e.g., cron / headless runners), claude -p may hang. This wrapper uses script -q -c ... /dev/null to allocate a pseudo-terminal.
Requirements
- Claude Code installed on the same host
claudebinary available (default expected:/home/ubuntu/.local/bin/claude)
Check:
claude --versionUsage
Basic headless prompt:
./scripts/claude_code_run.py -p "Return only the single word OK." --permission-mode planAllow tools (least privilege recommended):
./scripts/claude_code_run.py \
-p "Run tests and fix failures" \
--allowedTools "Bash,Read,Edit"Structured output:
./scripts/claude_code_run.py -p "Summarize this repo" --output-format json---
中文说明
这是一个让 Clawdbot 在服务器上通过 Claude Code CLI(claude) 运行任务的 skill。
仓库包含:
SKILL.md:给 Clawdbot 用的 skill 说明scripts/claude_code_run.py:对claude -p的封装(分配伪终端),用于避免在无 TTY 环境下卡住
使用方法
基础测试:
./scripts/claude_code_run.py -p "Return only the single word OK." --permission-mode plan允许工具(建议最小权限):
./scripts/claude_code_run.py \
-p "运行测试并修复失败" \
--allowedTools "Bash,Read,Edit"结构化输出:
./scripts/claude_code_run.py -p "总结这个仓库" --output-format json#!/usr/bin/env python3
"""Run Claude Code (claude CLI) reliably.
Default mode is *auto*:
- If the prompt looks like it uses interactive slash commands (e.g. /speckit.*)
we start an interactive Claude Code session in tmux (PTY).
- Otherwise we run headless (-p) through `script(1)` to force a pseudo-terminal.
Why this wrapper exists:
- Claude Code can hang when run without a TTY.
- CI / exec environments are often non-interactive.
Docs:
- Headless: https://code.claude.com/docs/en/headless
"""
from __future__ import annotations
import argparse
import os
import shlex
import subprocess
import sys
import time
from pathlib import Path
DEFAULT_CLAUDE = os.environ.get("CLAUDE_CODE_BIN", "/home/ubuntu/.local/bin/claude")
def which(name: str) -> str | None:
paths = os.environ.get("PATH", "").split(":")
for p in paths:
cand = Path(p) / name
try:
if cand.is_file() and os.access(cand, os.X_OK):
return str(cand)
except OSError:
pass
return None
def looks_like_slash_commands(prompt: str | None) -> bool:
if not prompt:
return False
for line in prompt.splitlines():
if line.strip().startswith("/"):
return True
return False
def build_headless_cmd(args: argparse.Namespace) -> list[str]:
cmd: list[str] = [args.claude_bin]
if args.permission_mode:
cmd += ["--permission-mode", args.permission_mode]
if args.prompt is not None:
cmd += ["-p", args.prompt]
if args.allowedTools:
cmd += ["--allowedTools", args.allowedTools]
if args.output_format:
cmd += ["--output-format", args.output_format]
if args.json_schema:
cmd += ["--json-schema", args.json_schema]
if args.append_system_prompt:
cmd += ["--append-system-prompt", args.append_system_prompt]
if args.system_prompt:
cmd += ["--system-prompt", args.system_prompt]
if args.continue_latest:
cmd.append("--continue")
if args.resume:
cmd += ["--resume", args.resume]
if args.extra:
cmd += args.extra
return cmd
def run_with_pty(cmd: list[str], cwd: str | None) -> int:
cmd_str = " ".join(shlex.quote(c) for c in cmd)
script_bin = which("script")
if not script_bin:
proc = subprocess.run(cmd, cwd=cwd, text=True)
return proc.returncode
proc = subprocess.run([script_bin, "-q", "-c", cmd_str, "/dev/null"], cwd=cwd, text=True)
return proc.returncode
def tmux_cmd(socket_path: str, *args: str) -> list[str]:
return ["tmux", "-S", socket_path, *args]
def tmux_capture(socket_path: str, target: str, lines: int = 200) -> str:
out = subprocess.check_output(
tmux_cmd(socket_path, "capture-pane", "-p", "-J", "-t", target, "-S", f"-{lines}"),
text=True,
)
return out
def tmux_wait_for_text(socket_path: str, target: str, pattern: str, timeout_s: int = 30, poll_s: float = 0.5) -> bool:
deadline = time.time() + timeout_s
while time.time() < deadline:
try:
buf = tmux_capture(socket_path, target, lines=200)
if pattern in buf:
return True
except subprocess.CalledProcessError:
pass
time.sleep(poll_s)
return False
def run_interactive_tmux(args: argparse.Namespace) -> int:
if not which("tmux"):
print("tmux not found in PATH; cannot run interactive mode.", file=sys.stderr)
return 2
socket_dir = args.tmux_socket_dir or os.environ.get("CLAWDBOT_TMUX_SOCKET_DIR") or f"{os.environ.get('TMPDIR', '/tmp')}/clawdbot-tmux-sockets"
Path(socket_dir).mkdir(parents=True, exist_ok=True)
socket_path = str(Path(socket_dir) / args.tmux_socket_name)
session = args.tmux_session
target = f"{session}:0.0"
subprocess.run(tmux_cmd(socket_path, "kill-session", "-t", session), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
subprocess.check_call(tmux_cmd(socket_path, "new", "-d", "-s", session, "-n", "shell"))
cwd = args.cwd or os.getcwd()
claude_parts = [args.claude_bin]
if args.permission_mode:
claude_parts += ["--permission-mode", args.permission_mode]
if args.allowedTools:
claude_parts += ["--allowedTools", args.allowedTools]
if args.append_system_prompt:
claude_parts += ["--append-system-prompt", args.append_system_prompt]
if args.system_prompt:
claude_parts += ["--system-prompt", args.system_prompt]
if args.continue_latest:
claude_parts.append("--continue")
if args.resume:
claude_parts += ["--resume", args.resume]
if args.extra:
claude_parts += args.extra
launch = f"cd {shlex.quote(cwd)} && " + " ".join(shlex.quote(p) for p in claude_parts)
subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "-l", "--", launch))
subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "Enter"))
# Workspace trust prompt (first run in a new folder).
if tmux_wait_for_text(socket_path, target, "Yes, I trust this folder", timeout_s=20):
subprocess.run(tmux_cmd(socket_path, "send-keys", "-t", target, "Enter"), check=False)
time.sleep(0.8)
if tmux_wait_for_text(socket_path, target, "Yes, I trust this folder", timeout_s=2):
subprocess.run(tmux_cmd(socket_path, "send-keys", "-t", target, "1"), check=False)
subprocess.run(tmux_cmd(socket_path, "send-keys", "-t", target, "Enter"), check=False)
if args.prompt:
for line in [ln for ln in args.prompt.splitlines() if ln.strip()]:
subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "-l", "--", line))
subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "Enter"))
time.sleep(args.interactive_send_delay_ms / 1000.0)
print("Started interactive Claude Code in tmux.")
print("To monitor:")
print(f" tmux -S {shlex.quote(socket_path)} attach -t {shlex.quote(session)}")
print("To snapshot output:")
print(f" tmux -S {shlex.quote(socket_path)} capture-pane -p -J -t {shlex.quote(target)} -S -200")
if args.interactive_wait_s > 0:
time.sleep(args.interactive_wait_s)
try:
snap = tmux_capture(socket_path, target, lines=200)
print("\n--- tmux snapshot (last 200 lines) ---\n")
print(snap)
except subprocess.CalledProcessError:
pass
return 0
def main() -> int:
ap = argparse.ArgumentParser(description="Run Claude Code reliably (headless or interactive via tmux)")
ap.add_argument("-p", "--prompt", help="Prompt text. In headless mode this is passed via -p. In interactive mode it is sent as keystrokes.")
ap.add_argument(
"--mode",
choices=["auto", "headless", "interactive"],
default="auto",
help="Execution mode. auto switches to interactive when prompt contains slash commands (lines starting with '/').",
)
ap.add_argument(
"--permission-mode",
default=None,
help=(
"Claude Code permission mode (passed through to `claude --permission-mode`). "
"Common values include: plan, acceptEdits, dontAsk, bypassPermissions, default."
),
)
ap.add_argument("--allowedTools", dest="allowedTools", help="Allowed tools allowlist string")
ap.add_argument("--output-format", dest="output_format", choices=["text", "json", "stream-json"], help="Output format (headless)")
ap.add_argument("--json-schema", dest="json_schema", help="JSON schema (string) when using --output-format json")
ap.add_argument("--append-system-prompt", dest="append_system_prompt", help="Append to Claude Code default system prompt")
ap.add_argument("--system-prompt", dest="system_prompt", help="Replace system prompt")
ap.add_argument("--continue", dest="continue_latest", action="store_true", help="Continue the most recent session")
ap.add_argument("--resume", help="Resume a specific session ID")
ap.add_argument(
"--claude-bin",
default=DEFAULT_CLAUDE,
help=f"Path to claude binary (default: {DEFAULT_CLAUDE}). You can also set CLAUDE_CODE_BIN.",
)
ap.add_argument("--cwd", help="Working directory to run claude in (defaults to current directory)")
ap.add_argument("--tmux-session", default="cc", help="tmux session name (interactive mode)")
ap.add_argument("--tmux-socket-dir", default=None, help="tmux socket dir (defaults to $CLAWDBOT_TMUX_SOCKET_DIR or /tmp)")
ap.add_argument("--tmux-socket-name", default="claude-code.sock", help="tmux socket file name")
ap.add_argument("--interactive-wait-s", type=int, default=0, help="Wait N seconds then print a tmux output snapshot")
ap.add_argument("--interactive-send-delay-ms", type=int, default=800, help="Delay between sending lines in interactive mode")
ap.add_argument("extra", nargs=argparse.REMAINDER, help="Extra args after --")
args = ap.parse_args()
extra = args.extra
if extra and extra[0] == "--":
extra = extra[1:]
args.extra = extra
if not Path(args.claude_bin).exists():
print(f"claude binary not found: {args.claude_bin}", file=sys.stderr)
print("Tip: set CLAUDE_CODE_BIN=/path/to/claude", file=sys.stderr)
return 2
mode = args.mode
if mode == "auto" and looks_like_slash_commands(args.prompt):
mode = "interactive"
if mode == "interactive":
return run_interactive_tmux(args)
cmd = build_headless_cmd(args)
return run_with_pty(cmd, cwd=args.cwd)
if __name__ == "__main__":
raise SystemExit(main())