Agent skill

Canvas Execute

by X-isdoingreat in X-isdoingreat/canvas-pilot

This skill should be used when executing an already-scanned Canvas plan after the user has approved it.

AGPL-3.0Auto-check: notes

Install Canvas Execute

skills CLI
$ npx skills add X-isdoingreat/canvas-pilot --skill canvas-execute -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install X-isdoingreat/canvas-pilot canvas-execute --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/X-isdoingreat/canvas-pilot.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/canvas-execute .claude/skills/canvas-execute && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
canvas-execute
GitHub stars
125
Token cost
~6.6k tokens
SKILL.md length
2,578 words
Files
1
Skills in repo
32
Repo updated
First seen
Licence
AGPL-3.0

At a glance

This skill should be used when executing an already-scanned Canvas plan after the user has approved it.

  • Works in 9 steps: Precondition check → Handle stale marker from a prior crashed… → Activate today's marker (with session id) → …
  • SKILL.md covers The contract with canvas-scan, Hook guardrails (what gates…, Working directory assumption and What you do, plus 2 more sections
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

What it does

Canvas Execute is an agent skill from X-isdoingreat/canvas-pilot. This skill should be used when executing an already-scanned Canvas plan after the user has approved it. Invoked after canvas-scan wrote runs/<today/plan.json and the user replied with approval like "批准全部", "approve all", "做 1, 3, 5", "只做 urgent", "第 N 项 defer", "cancel". Parses the approval, updates plan.json with per-item decisions, dispatches approved items to course-specific sub-skills (canvas-ics33 / canvas-reading-annotation / canvas-essay / canvas-inside / canvas-zybooks / canvas-generic / canvas-skip)…

Its SKILL.md is about 6.6k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It works with Bash. The repository describes itself as: Local-first Canvas LMS AI agent that learns each course's recurring assignment workflow and reuses it through scan - approval - execute with student review. The licence is AGPL-3.0.

Example prompts

  • “approve all”
  • “做 1, 3, 5”
  • “只做 urgent”
  • “/canvas-execute”

Requirements

  • Python 3
  • Pre-approved tools (allowed-tools): Bash, Read, Write, Edit, Glob, Grep, WebFetch, Skill, TodoWrite

Workflow steps

9 steps, taken from the step headings in SKILL.md.

  1. Precondition check
  2. Handle stale marker from a prior crashed session
  3. Activate today's marker (with session id)
  4. Parse user's approval + update plan.json
  5. Dispatch approved items — one at a time, in order
  6. Pause when session is running tight — report clearly and ask
  7. Finalize — write any remaining deferred, REPORT.md, final_drafts/ sync, rm marker
  8. Urgent banner at top of REPORT.md
  9. Sync final_drafts/ folder

What it can do on your machine

Read from SKILL.md and the folder at commit 6b79d5b. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves these tools, so the agent can use them without asking each time:

    • Bash
    • Read
    • Write
    • Edit
    • Glob
    • Grep
    • WebFetch
    • Skill
    • TodoWrite

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are python, markdown, bash and json).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Canvas Execute loads about 6.6k tokens when it runs. Until then it costs about 164 tokens; SKILL.md has 2,578 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~164
When it runs · the whole SKILL.md, loaded when a task matches
~6.6k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check: notes

The automated check noted patterns worth knowing about, such as sudo or a known installer.

  • NoteMentions a .env fileSKILL.md:50
    thon helpers under `src/`, config under `.env` and `courses.yaml`.
  • NotePre-approves every shell command (allowed-tools: Bash)SKILL.md
    allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, Skill, TodoWrite

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from X-isdoingreat/canvas-pilot at commit 6b79d5b, republished under its AGPL-3.0 licence (© X-isdoingreat). 2,578 words, ~6,572 tokens.

Download SKILL.mdSave it as .claude/skills/canvas-execute/SKILL.md (or your agent's skills folder).
name
canvas-execute
description
This skill should be used when executing an already-scanned Canvas plan after the user has approved it. Invoked after `canvas-scan` wrote `runs/<today>/plan.json` and the user replied with approval like "批准全部", "approve all", "做 1, 3, 5", "只做 urgent", "第 N 项 defer", "cancel". Parses the approval, updates plan.json with per-item decisions, dispatches approved items to course-specific sub-skills (canvas-ics33 / canvas-reading-annotation / canvas-essay / canvas-inside / canvas-zybooks / canvas-generic / canvas-skip), writes skipped+deferred result.json for non-approved items, then produces REPORT.md and syncs the final_drafts/ folder.
allowed-tools
Bash, Read, Write, Edit, Glob, Grep, WebFetch, Skill, TodoWrite

canvas-execute (approval-gated dispatch)

Dispatcher for the Canvas auto-homework system. This skill is the execute half of the scan/execute split — it runs only after canvas-scan has produced a plan and the user has explicitly approved some subset of it.

The contract with canvas-scan

  • canvas-scan wrote runs/<today>/plan.json with every pending item at user_decision: null.
  • The user reviewed the plan (printed as a markdown table in the previous turn) and replied with an approval spec.
  • Claude (the outer orchestrator) parsed the user's reply and invoked this skill, passing the approval interpretation as an argument or as context.

This skill's job: apply the approval to plan.json, dispatch the approved items, record the non-approved ones as deferred, finalize.

If plan.json doesn't exist or is >24h old → STOP and tell the user to run /canvas-scan first. Do not invent a plan, do not dispatch anything, do not guess.

The highest spec is canvas_scan.md §7 (batch execution after approval). Read it if unsure.

Hook guardrails (what gates execute)

All 7 hooks apply during execute:

#HookEffect
1SessionStart inject-context.pyInjects pending list at turn 0. Informational.
2PostToolUse(Write/Edit on result.json) check-result-schema.pyEvery result.json you write must have valid status (draft_ready/submitted/skipped/error). Bad schema → exit 2.
3PostToolUse(Bash) check-bash-output.pyValidates pytest / coverage / git output when sub-skills run them.
4PreToolUse(Bash) check-presubmit-audit.pyBlocks cv.submit_files / upload_submission_file / submit_quiz unless <work>/verification.log exists and is all PASS.
5PostToolUse(Write/Edit on result.json) check-spec-grounding.pyFor draft_ready/submitted, if spec.md mentions external refs but references/ is empty → exit 2.
6PostToolUse(Write/Edit on result.json) check-identifier-grounding.pyFor .py/.pdf drafts, every suspicious Python identifier must be grounded in spec/refs/builtins.
7Stop hook check-router-complete.pyGated by runs/<today>/.scan_in_progress marker. While the marker exists, every item in assignments.json must have a result.json before Stop passes.

Critical: hook 7 is the one that keeps execute honest. Without it, a session could dispatch 3 of 5 approved items and then drift off. With it, you cannot stop until every assignment in assignments.json has a result.json — either written by a sub-skill (for approved+executed) or written by this skill as status: skipped with deferred_to_next_run: true (for non-approved or skipped-at-approval-time).

Working directory assumption

The working directory is the project root (the folder you cloned this repo into; Claude Code sets cwd automatically on session start). Python helpers under src/, config under .env and courses.yaml.

What you do

0. Precondition check
bash
TODAY=$(date +%Y-%m-%d)
test -f "runs/$TODAY/plan.json" || { echo "NO_PLAN"; exit 1; }
test -f "runs/$TODAY/assignments.json" || { echo "NO_ASSIGNMENTS"; exit 1; }

If either is missing: tell the user "No plan.json for today. Run /canvas-scan first to generate a plan, review it, then come back." Stop. Do NOT run scan inline — that's the router's job and would defeat the approval gate.

Then check plan.json freshness:

python
import json, datetime as dt
plan = json.loads(open(f"runs/{today}/plan.json", encoding="utf-8").read())
expires = dt.datetime.fromisoformat(plan["expires_at"])
now = dt.datetime.now(expires.tzinfo)
if now > expires:
    print("PLAN_EXPIRED")
    # tell user: "Plan is stale (>24h). Re-run /canvas-scan before executing."

If expired: STOP. Tell user to rerun /canvas-scan.

1. Handle stale marker from a prior crashed session

Before activating your own marker, check for an orphan marker from a past session:

bash
if [ -f "runs/<past-date>/.scan_in_progress" ]; then ...

Glob runs/*/.scan_in_progress. For each match whose date is not today:

  • Read that day's assignments.json
  • For any assignment lacking a result.json, write {status: "skipped", notes: "session crashed before this item was processed", deferred_to_next_run: true} so the next /canvas-scan re-proposes it
  • Delete the orphan marker

This keeps Stop hook from being permanently wedged by a past crash.

2. Activate today's marker (with session id)

The marker file's contents are the creating session's id, not just a touch. The Stop hook (check-router-complete.py) reads the marker contents and compares to its own event's session_id — if they don't match, the hook treats this as "another session's marker, not mine" and passes through. This is what lets a debug-only session (in the same project, never called canvas-execute) stop cleanly while another session is mid-execute.

CC must extract the current session id from the env / hook event / transcript path and write it into the marker:

python
import os, uuid
from pathlib import Path
import datetime as dt

today = dt.date.today().isoformat()
marker = Path(f"runs/{today}/.scan_in_progress")
marker.parent.mkdir(parents=True, exist_ok=True)

# Determine session_id. CC has its own session_id available in the runtime
# environment — check CLAUDE_SESSION_ID env var first, fall back to extracting
# from the transcript filename ($CLAUDE_TRANSCRIPT_PATH or the conversation
# transcript path), and as a last resort generate a uuid (this still works:
# the hook only requires "marker contents == event.session_id"; if execute
# stamped a uuid that doesn't match the runtime session id, the hook treats
# the session as not-the-owner and passes through, which is safe — you just
# lose the gate for this run, but no data loss).
session_id = (
    os.environ.get("CLAUDE_SESSION_ID")
    or os.environ.get("CC_SESSION_ID")
    or str(uuid.uuid4())  # safe fallback
)
marker.write_text(session_id, encoding="utf-8")

Practical note for CC: at execute time, read the most reliable session id source you have — typically the transcript path filename matches the session uuid. If you can't determine the current session id deterministically, use uuid4 as fallback; this still implements "this session vs other sessions" correctly within this single execute run.

This arms the Stop hook. Every assignment in runs/$TODAY/assignments.json must now have a matching result.json before this session can stop — for sessions whose session_id matches the marker contents. Other sessions in the same project pass through.

3. Parse user's approval + update plan.json

Read the user's approval spec. It was either:

  • Passed as an argument when Claude invoked this skill (e.g. "approve all", "只做 urgent", "做 1, 3, 5")
  • Or visible in the preceding conversation turn

Recognize these patterns (Chinese + English):

User saysInterpretation
批准全部 / 全部批准 / approve all / all / 全部做every item → approve
只做 urgent / urgent only / 只做紧急items with bucket: urgent → approve, rest → defer
做 1, 3, 5 / 做 1 3 5 / approve 1 3 5listed indices → approve, rest → defer
做 1-4 / 1 到 4index range → approve, rest → defer
第 N 项 用 canvas-X / swap N to canvas-Xitem N gets swap:canvas-X, still approved but with different skill
第 N 项 defer / 跳过 N / skip Nitem N → defer
cancel / 取消 / 全部取消every item → defer (nothing dispatched, all written as skipped+deferred)
未明示 (e.g. user says "做 1, 3" silent on 2,4,5)2, 4, 5 → defer (safer than auto-executing)

Ambiguous or unparseable input → STOP and ask the user once more. Do not guess. Example ambiguous input: "做前面几个" (how many is "前面"?). Answer: "请明确一下 —— 做第 1、2、3 项吗?" and wait.

Update plan.json atomically:

python
import json, os
plan_path = Path(f"runs/{today}/plan.json")
plan = json.loads(plan_path.read_text(encoding="utf-8"))
for item in plan["items"]:
    item["user_decision"] = determine_decision(item)  # "approve" | "defer" | "swap:canvas-X"
tmp = plan_path.with_suffix(".json.tmp")
tmp.write_text(json.dumps(plan, indent=2, ensure_ascii=False), encoding="utf-8")
os.replace(tmp, plan_path)
4. Dispatch approved items — one at a time, in order

For each item in plan.json where user_decision == "approve" or starts with "swap:", process sequentially (plan.json items are already sorted by bucket priority then by hours_left, so earliest-due-first).

For each item:

  1. Determine which sub-skill to invoke:

    • user_decision == "swap:canvas-X" → canvas-X (user explicit override wins)

    • user_decision == "approve" → resolve the Skill name from proposed_skill. proposed_skill may be a short courses.yaml route value OR already a canvas-* Skill name; map it to the Skill to invoke:

      route value (proposed_skill)Skill to invoke
      quizcanvas-inside
      code_pycanvas-ics33
      ac_englishcanvas-reading-annotation (or canvas-essay via the sub-router below)
      zybookscanvas-zybooks
      mixed_unsupportedcanvas-skip

      A proposed_skill already in canvas-* form (e.g. the LDB filter rewrites quiz items to canvas-skip) maps to itself.

    • Writing-course sub-routing: if the resolved skill is canvas-reading-annotation, run the deterministic router to decide between the short-form handler (canvas-reading-annotation, unchanged) and the long-essay handler (canvas-essay). This is a pure-Python utility — 0 LLM calls, regex + numeric comparison + Canvas API field queries.

      python
      if proposed_skill == "canvas-reading-annotation":
          import re, yaml
          from pathlib import Path
          from src.ac_eng_router import route_ac_eng_assignment
          from src import canvas_client as cv
      
          # Pull the overlay's persona_trigger_patterns / persona_skip_patterns yaml block
          overlay_path = Path("_private/canvas-essay-app.md")
          overlay_config = {}
          if overlay_path.exists():
              text = overlay_path.read_text(encoding="utf-8")
              m = re.search(r"```yaml\s*\n(persona_trigger_patterns:.*?)\n```",
                            text, re.DOTALL)
              if m:
                  overlay_config = yaml.safe_load(m.group(1)) or {}
      
          # Look up the full assignment dict (router needs name, description,
          # points_possible, submission_types, attached_pdf_texts).
          full = next(
              (a for a in assignments_json if str(a.get("id")) == str(item["assignment_id"])),
              None,
          )
          if full is None:
              full = cv.get_assignment(item["course_id"], item["assignment_id"])
      
          sub = route_ac_eng_assignment(full, overlay_config, plan_item=item)
          if sub == "essay":
              proposed_skill = "canvas-essay"
          # else keep canvas-reading-annotation for short-form path

      If _private/canvas-essay-app.md does not exist, overlay_config stays empty — the router still works (skips Layer 2, relies on Layers 3-6). The router never raises; on any malformed regex in the overlay it logs and falls through.

  2. TodoWrite: add the assignment as a todo, mark in_progress.

  3. Invoke via the Skill tool. Pass a brief context line:

    "Work on <assignment_name> (course <course_name>). Work dir: runs/<today>/<work_dir>. See assignments.json and plan.json for full item details."

  4. Sub-skill runs → writes its own result.json → returns.

  5. Read the result.json. Mark todo completed (or keep in_progress with error note if status: error).

  6. Update runs/_processed.json ledger (atomic write via .tmp + os.replace):

    python
    ledger[f"{course_id}:{assignment_id}"] = {
        "status": sub_result["status"],
        "course_name": item["course_name"],
        "assignment_name": item["assignment_name"],
        "due_at": item["due_at"],
        "completed_at": dt.datetime.now(dt.timezone.utc).isoformat(),
        "draft_path": sub_result.get("draft_path"),
        "notes": sub_result.get("notes"),
        "deferred_to_next_run": False,
    }
  7. Continue to the next approved item.

Run items sequentially (not in parallel) — Canvas writes can race, and sequential logs are easier to debug.

If the Skill tool reports the target sub-skill is not discoverable, STOP and print a clear error ("sub-skill canvas-X not found; CC was likely launched from the wrong directory"). Do NOT try to do the work inline.

5. Pause when session is running tight — report clearly and ask

Claude judges its own context budget. If roughly less than ~15 turns of capacity remain AND there are still approved items left to dispatch, break out of the loop instead of trying to squeeze in another heavy item. Half-finished work is worse than deferred work.

To pause cleanly (Stop hook requires every assignments.json item to have a result.json):

  1. For each remaining undispatched approved item, write a placeholder result.json:

    json
    {
      "assignment_id": ...,
      "course_id": ...,
      "status": "skipped",
      "notes": "paused mid-run — awaiting user decision (continue or defer)",
      "deferred_to_next_run": true
    }

    Also update _processed.json ledger with deferred_to_next_run: true. This satisfies the Stop hook.

  2. Print a clear status to the user, in this shape:

    做完了:
    - #1 Quiz on Section 5 — draft_ready (runs/.../quiz_s5.json)
    - #2 Quiz on Section 6 — draft_ready (runs/.../quiz_s6.json)
    
    还没做(session 快满了):
    - #3 Set 3 Problem 1
    - #4 Set 3 Problem 2
    - #7 Tue Wk5 HW Scan
    
    要继续做吗?
    - 回 "继续" → 我接着跑 #3,撑到哪算哪
    - 回 "defer" / "算了" / 不回 → 剩下的保持 deferred,下次 /canvas-scan 自动重现
  3. End your turn. Do NOT proceed to §6 finalize yet — wait for the user's next reply.

  4. On the user's next turn:

    • "继续" / "接着做" / "yes" → go back to §4 and dispatch the next undispatched item. Each successful sub-skill completion overwrites the placeholder result.json with a real one. Repeat §5 pause-check as you go.
    • "defer" / "算了" / "跳过" / silence / anything not a clear continue → jump to §6 finalize. Placeholder result.json files stay as the final state (they're already correctly marked deferred_to_next_run: true).

Rule of thumb on when to pause: if the next item's proposed_skill is canvas-ics33 (heavy, ~20 turns) or canvas-generic (heavy, ~25-30 turns — investigation + 3 sub-agent reviews) and your context window is >60% consumed → pause. If it's canvas-inside or canvas-skip (light, ~10 or ~1 turns) → probably fine to attempt one more. Trust your judgment; erring toward pausing is safer than runaway.

Show full SKILL.md (1,054 more words)Show less
6. Finalize — write any remaining deferred, REPORT.md, final_drafts/ sync, rm marker

Reach this section via one of two paths:

  • Happy path: §4 loop completed all approved items.
  • Defer path: user replied "defer" / "算了" / silence after §5's pause+ask.

Steps:

  1. Write deferred result.json for items not yet written. These are items where:

    • user_decision == "defer" (explicitly declined at approval time) — if their result.json doesn't already exist
    • user_decision == null (user silent on this index at approval time) — same
    • Placeholder-paused items from §5 — already written, skip Use: {status: "skipped", notes: "user declined at plan review" / "not approved this run", deferred_to_next_run: true}
  2. Update runs/_processed.json ledger for every item processed this run (dispatched or deferred).

  3. Write runs/<today>/REPORT.md (see §7 for urgent banner + §8 for layout).

  4. Sync final_drafts/ folder for any draft_ready/submitted items (see §9).

  5. Remove marker: rm "runs/$TODAY/.scan_in_progress". Pair with §2's touch. If this step is skipped, the next session in this directory is gated.

7. Urgent banner at top of REPORT.md

Compute urgency for EVERY item in assignments.json (whether approved, deferred, or already done):

python
import json, datetime as dt
from pathlib import Path

now = dt.datetime.now(dt.timezone.utc)
ledger = json.loads(Path("runs/_processed.json").read_text(encoding="utf-8"))
todays = json.loads(Path(f"runs/{today}/assignments.json").read_text(encoding="utf-8"))

urgent = []
for item in todays:
    key = f"{item['course_id']}:{item['assignment_id']}"
    led = ledger.get(key, {})

    # Canvas is source of truth for "is it submitted"
    try:
        sub = cv.get_submission(item["course_id"], item["assignment_id"])
        live_state = sub.get("workflow_state")
    except Exception:
        live_state = "?"

    if live_state in ("submitted", "graded"):
        continue  # done, skip from urgent banner

    due = dt.datetime.fromisoformat(item["due_at"].replace("Z", "+00:00"))
    hours_left = (due - now).total_seconds() / 3600
    if hours_left <= 24:
        # Prefer friendly name if the overlay set one (plan.json carried it through from scan)
        course_display = item.get("course_friendly_name") or item["course_name"]
        urgent.append({
            "course": course_display[:25],
            "name": item["name"][:50],
            "hours_left": round(hours_left, 1),
            "state": live_state if hours_left > 0 else "OVERDUE",
            "ledger_state": led.get("status"),
            "draft": led.get("draft_path"),
            "skill": item["skill"],
        })

The same friendly-name preference applies to the error block (§7.5), the success summary, and the final_drafts/ folder structure — wherever a course label is rendered for the student, course_friendly_name (when present in plan.json/result.json) takes precedence over course_name. The framework name (e.g. canvas-ics33) is the dispatch key and is unaffected.

Format banner:

markdown
# 🔥 URGENT — N item(s) due within 24h, not submitted

- [canvas-ics33] <Code Course> Spring 2026 | Set 3 Problem 1 | due in 14h | state=unsubmitted | draft: runs/.../set3p1.py
- [canvas-reading-annotation] <Writing Course> S26     | Tue Wk5 HW Scan | OVERDUE 3h ago | state=OVERDUE | ledger=draft_ready

Upload the draft, mark 'skip on purpose', or handle it. This banner reappears every run until resolved.

---

If no urgent items:

markdown
# ✅ No urgent items in next 24h

---

The banner is ALWAYS the first block of REPORT.md. The CEO opens the file and sees status immediately.

7.5. Errors get hand-holding, not just notes

For every item where this run wrote result.json with status: "error", REPORT.md gets a debug-help block right below the urgent banner. The student sees a concrete next-step list, not just notes: "...".

Iterate runs/<today>/*/result.json, collect the ones with status == "error", and for each render:

markdown
## ⚠️ Errors this run — N item(s) need fixing

### {assignment_name}  ({course_name})

- skill: `canvas-{name}` → `.claude/skills/canvas-{name}/SKILL.md`
- error notes: {result.json notes verbatim}

**Debug checklist** (open the SKILL.md and tick through):

- [ ] `<!-- UNFILLED_SKELETON v1 -->` sentinel still present? Remove it after you fill the 4 TODOs.
- [ ] Frontmatter `name:` matches the directory name (`canvas-{name}`)?
- [ ] Frontmatter `allowed-tools:` includes everything the skill uses (Bash / Read / Write / Edit / WebFetch)?
- [ ] §1 TODO answered (where does the real spec live)?
- [ ] §2 TODO answered (how does this skill produce a draft)?
- [ ] §3 TODO answered (how do you verify the draft before submitting)?
- [ ] §4 result.json — does the skill actually write `runs/<today>/<dir>/result.json`?
- [ ] If `notes` mentions a specific file path or API call: does that path exist? does the API call work standalone (`python -c "from src import canvas_client; ..."`)?

After fixing, re-run `/canvas-scan` — the assignment will reappear in the plan (deferred items re-enter on next scan).

---

If multiple errors, list them all under one ## ⚠️ Errors this run heading. If zero errors, skip this section entirely (don't render an empty heading).

This block exists because raw error notes is hostile to a first-time student — they get "X failed" and don't know whether the problem is their SKILL.md, the framework, or Canvas. The checklist gives them a 30-second sanity scan + a specific re-run command.

7.6. Next step block at the bottom of REPORT.md

Same principle as canvas-scan §5b: data without recommendation is a bug. After all status sections, REPORT.md ends with a ## Next step block giving ONE concrete suggested action.

The suggestion adapts to what actually happened this run:

  • Errors present: "先看 errors 里的 [first error's assignment name],根据 debug checklist 检查 SKILL.md 是不是还有 TODO 没填。修完再 /canvas-scan 重跑。" / English equivalent.
  • Skipped present (manual courses), no errors: "[N] 项是手动课,去 Canvas 自己交:<list assignment names>." / English equivalent.
  • Done items only, no errors / skipped: "草稿在 runs/<today>/,审完手动上传到 Canvas (or your skill's submission flow)." / English equivalent.
  • Mix: pick the highest-priority single action — errors first, then skipped, then done.

Render exactly one ## Next step block. Single sentence preferred; multiple bullets allowed only when there are multiple distinct actions of equal urgency. Keep the language matched to the rest of REPORT.md (Chinese if the run was triggered by a Chinese-language scan, English otherwise — same sniff rule as canvas-scan §5b).

8. Sync final_drafts/ folder

For every item from this run whose final status is draft_ready or submitted and has a draft_path:

  1. Map course → subfolder:

    skill / course patternsubfolder
    code_py / the code coursefinal_drafts/CODE/
    zybooks / the zyBooks math coursefinal_drafts/ZYBOOKS/
    ac_english / the writing coursefinal_drafts/WRITING/
    quiz / the quiz coursefinal_drafts/QUIZ/
    anything elsefinal_drafts/_other/
  2. Derive filename: strip course prefix from assignment name, _ for spaces, keep extension.

  3. Copy:

    python
    import shutil
    from pathlib import Path
    dest_dir = Path("final_drafts") / subfolder
    dest_dir.mkdir(parents=True, exist_ok=True)
    shutil.copy2(draft_path, dest_dir / short_name)
  4. Rewrite final_drafts/README.txt entirely:

    • One line per file: filename STATUS (due DATE)
    • STATUS = ✅ 已交 Canvas / ⏳ 待上传 / 🔧 待答题
    • Rescans the final_drafts/ tree + cross-refs _processed.json for status

The final_drafts/ folder is the CEO's easy-find drop zone for anything to upload. Skip this and drafts rot unseen.

(The .scan_in_progress marker is removed in §6 step 5 as the final step of finalize, not in a separate numbered section.)

Crash-path fallback: if dispatch crashed mid-run (sub-skill threw, Canvas API died, etc.) and you're recovering in a follow-up turn before marker removal: make sure every item in assignments.json has a result.json (write status: skipped, notes: "execute crashed at this item", deferred_to_next_run: true for unfinished ones). Then §6 step 5 removes marker. Then report the crash to the user with specifics.

What you MUST NOT do

  • Do NOT dispatch an item whose user_decision isn't in {approve, swap:*}. "Not approved" means "not approved". Silent over-execution is the exact failure mode we're preventing.
  • Do NOT scan Canvas or regenerate plan.json from scratch. If plan.json is missing or stale, hand back to /canvas-scan.
  • Do NOT try to rush the last approved item when context is tight. Pause (§5), report clearly, ask. Half-finished work is worse than deferred work — deferred items cleanly reappear on the next /canvas-scan.
  • Do NOT fabricate draft_path or status in the ledger. If a sub-skill returned error, record error — don't round up to draft_ready.
  • Do NOT submit anything to Canvas without standing authorization. Per SECRETS.md, the standing auto-submit authorizations are limited (the quiz course under CANVAS_QUIZ_AUTORUN=1, the code course under its own explicit flag). Everything else is draft-only; upload is the user's call.
  • Do NOT forget to remove the marker. The single biggest lesson from 2026-04-21 was "don't leave the Stop gate wedged open".

Relationship to the old monolithic canvas-router

The old canvas-router used to do scan + dispatch + finalize all in one skill. It was split because a single SKILL.md's "stop and wait for approval" instruction was too easy to drift past. The split makes the approval gate a filesystem boundary (plan.json on disk between two Skill invocations) instead of a prose boundary (a paragraph in a SKILL.md).

Earlier versions of this (canvas-execute) skill also had a "pre-dispatch session-budget guard" that estimated total turns before dispatching and refused if >60. That was over-engineered: it fired after the user had already approved, creating a frustrating "batched, then blocked" loop. The real mechanism now (§5 mid-run pause-and-ask) is simpler: just do them one at a time, stop when it feels tight, report clearly, ask whether to continue.

Whenever you're tempted to "just also do the scan inline if plan.json is missing", remember: that temptation IS the bug. Hand back to the user, let them run /canvas-scan, start fresh with a real plan.

© X-isdoingreat, AGPL-3.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .claude/skills/canvas-execute of X-isdoingreat/canvas-pilot.

Open the folder on GitHubat commit 6b79d5b

Compare with similar skills

Canvas Execute next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Canvas Execute compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Canvas Execute this skillX-isdoingreat/canvas-pilot125—~6.6kAutomated safety check: NotesAGPL-3.0
Hook Development for Claude Code Pluginsanthropics/claude-plugins-official38k10 repos~4.1kAutomated safety check: NotesApache-2.0
Plugin Settings Patternanthropics/claude-plugins-official38k7 repos~3kAutomated safety check: PassApache-2.0
Mole Bug Patternstw93/Mole70k—~2kAutomated safety check: PassGPL-3.0
Neat-Freak Knowledge CloseoutKKKKhazix/khazix-skills21k—~1.9kAutomated safety check: PassMIT
E2Ecallstack/react-native-pager-view3.4k1 repos~2.1kAutomated safety check: PassMIT

Similar skills

  • Hook Development for Claude Code Plugins

    anthropics/claude-plugins-official

    Official

    Explains how to write Claude Code plugin hooks, both prompt-based checks and bash commands, for events such as PreToolUse, Stop and SessionStart.

    38k GitHub starsUsed in 10 repos~4.1k tokens
    Agent WorkflowsAuto-check: notes
  • Plugin Settings Pattern

    anthropics/claude-plugins-official

    Official

    Shows how Claude Code plugins keep per-project settings and state in .claude/plugin-name.local.md files with YAML frontmatter and a markdown body.

    38k GitHub starsUsed in 7 repos~3k tokens
    Agent WorkflowsAuto-check passed
  • A catalog of recurring bug shapes in the Mole Mac cleaner, used to review safety-sensitive diffs for deletion safety, unbounded commands, shell traps and weak tests.

    70k GitHub stars~2k tokensUpdated today
    DevelopmentAuto-check passed
  • Neat-Freak Knowledge Closeout

    KKKKhazix/khazix-skills

    Brings project docs, agent rule files, authorized memory and leftover workspace files back in line with what the code and runtime actually do at the end of a work session.

    21k GitHub stars~1.9k tokensUpdated 7 days ago
    Agent WorkflowsAuto-check passed
  • E2E

    callstack/react-native-pager-view

    Agentic end-to-end tests with e2e, the e2e runner. An agent skill from callstack/react-native-pager-view.

    3.4k GitHub starsUsed in 1 repo~2.1k tokens
    Testing & QAAuto-check passed
  • Runs a spec-driven workflow from PRD to epic to GitHub issues to parallel agents, with status, standup and blocked-work reports from bundled scripts.

    8.4k GitHub stars~1.1k tokensUpdated 6 mo ago
    Product & Project ManagementAuto-check passed

More from X-isdoingreat/canvas-pilot

All 32 skills in this repo
  • Daily Work Tweet

    X-isdoingreat/canvas-pilot

    A skill your agent uses when verified work from today or another day should become an X/Twitter post, build-in-public update, ship log, or bilingual draft.

    125 GitHub stars~1.6k tokensUpdated 2 mo ago
    Auto-check passed
  • Canvas Awkward Syntax

    X-isdoingreat/canvas-pilot

    A skill your agent uses when a short local academic draft needs role-aware syntax diversification while preserving meaning, locks, source grounding, rubric-critical openings, and document structure.

    125 GitHub stars~2k tokensUpdated 2 mo ago
    Auto-check passed
  • Canvas Cron

    X-isdoingreat/canvas-pilot

    A skill your agent uses when managing Canvas Pilot schedules: install, inspect, pause, change, delete, or safely test scheduled scans and runs.

    125 GitHub stars~2.1k tokensUpdated 2 mo ago
    Auto-check: notes
  • Canvas Essay

    X-isdoingreat/canvas-pilot

    A skill your agent uses for an approved long academic-writing assignment routed by canvas-execute after the deterministic writing router selects essay.

    125 GitHub stars~2.2k tokensUpdated 2 mo ago
    Auto-check passed
  • Canvas Generic

    X-isdoingreat/canvas-pilot

    A skill your agent uses for an approved Canvas assignment that no specialized course skill can handle.

    125 GitHub stars~2.4k tokensUpdated 2 mo ago
    Auto-check passed
  • Canvas Humanizer

    X-isdoingreat/canvas-pilot

    A skill your agent uses when a local academic draft needs a meaning-preserving humanizing pass with less uniform syntax while retaining rubric, source, lock, voice, and length constraints.

    125 GitHub stars~1.8k tokensUpdated 2 mo ago
    Auto-check passed

Works with

Questions about Canvas Execute

What does Canvas Execute do?

This skill should be used when executing an already-scanned Canvas plan after the user has approved it. Canvas Execute is an agent skill from X-isdoingreat/canvas-pilot. This skill should be used when executing an already-scanned Canvas plan after the user has approved it.

How do I install Canvas Execute in Claude Code?

Run `npx skills add X-isdoingreat/canvas-pilot --skill canvas-execute -a claude-code`. Or copy the skill folder (.claude/skills/canvas-execute in X-isdoingreat/canvas-pilot) into .claude/skills/canvas-execute in your project. Claude Code loads it when a task matches its description.

How do I install Canvas Execute in Codex?

Run `npx skills add X-isdoingreat/canvas-pilot --skill canvas-execute -a codex`. Or copy the skill folder (.claude/skills/canvas-execute in X-isdoingreat/canvas-pilot) into .agents/skills/canvas-execute in your project. Codex loads it when a task matches its description.

Can I use Canvas Execute in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add X-isdoingreat/canvas-pilot --skill canvas-execute -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/canvas-execute, .gemini/skills/canvas-execute, .github/skills/canvas-execute and .opencode/skills/canvas-execute in your project.

What does Canvas Execute need to run?

SKILL.md names no scripts, command-line tools or credentials: Canvas Execute is instructions for the agent only. Our summary lists: Python 3. Its frontmatter pre-approves these tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, Skill, TodoWrite.

Does Canvas Execute access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Canvas Execute safe to install?

Our automated static check of SKILL.md found notes only (mentions a .env file; pre-approves every shell command (allowed-tools: bash)), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.

What licence does Canvas Execute use?

Canvas Execute is published under the AGPL-3.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Canvas Execute use?

About 6.6k tokens (SKILL.md is roughly 26k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Canvas Execute?

Skills that share tags, products or a category with Canvas Execute: Hook Development for Claude Code Plugins (anthropics/claude-plugins-official, 38k stars), Plugin Settings Pattern (anthropics/claude-plugins-official, 38k stars), Mole Bug Patterns (tw93/Mole, 70k stars) and Neat-Freak Knowledge Closeout (KKKKhazix/khazix-skills, 21k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Canvas Execute?

X-isdoingreat (a GitHub user) maintains it in X-isdoingreat/canvas-pilot, which has 125 GitHub stars. The repository holds 32 skills in this directory. The repository was last updated on July 31, 2026.

Source: X-isdoingreat/canvas-pilot on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.