| name | cron-job-provider-migration |
| category | devops |
| description | Migrate cron jobs from one LLM provider to another. Includes direct endpoint verification (user preference 2026-06-01), the yidong/minimax-cn API quirks, and the surprising fact that `cronjob update` does NOT change provider/model/base_url — those need direct jobs.json edits. |
Cron Job Provider Migration
Migrate cron jobs from one LLM provider to another.
User preference: verify the endpoint FIRST, not the metadata
When a provider migration is suspect, the user wants you to directly verify the LLM endpoint by calling it with a known prompt and inspecting the response. Examples from 2026-06-01:
- "立刻验证 yidong 是否 work" — point urllib at the endpoint, confirm HTTP 200 + sane response
- "我觉得根本问题还是llm那块" — bias toward endpoint probing, not jobs.json / cron list / agent.log reading
This is opposite to "investigate jobs.json metadata" (last_run_at, last_status, cron list). The user considers the endpoint ground truth — metadata is downstream and may lie.
The validated Python probe lives in scripts/migrate-cron-provider.py (call as migrate-cron-provider.py <provider>) and the reference doc references/yidong-provider-verification.md covers the four custom providers + the yidong model-rewrite and reasoning-content quirks.
Steps (the actual workflow that works)
1. Probe the new provider endpoint
python3 ~/.hermes/skills/devops/cron-job-provider-migration/scripts/migrate-cron-provider.py <provider_name>
The script does: read config.yaml > custom_providers, find the named entry, fire a minimal chat/completions call with the right auth header, assert HTTP 200 + non-empty choices[0]. If the probe fails, abort the migration. No amount of jobs.json editing will fix a broken endpoint.
2. Locate the job
import json
data = json.load(open('/Users/jinguo/.hermes/cron/jobs.json'))
for j in data['jobs']:
if j['name'] == '<name>':
print(j['id'], j.get('provider'), j.get('model'), j.get('base_url'))
3. CRITICAL — cronjob update CANNOT change provider/model/base_url
Correction (2026-06-01): the cronjob tool's update action accepts only these fields: name, prompt, skills, schedule, deliver, context_from, enabled_toolsets, workdir, profile, repeat, script, no_agent. It does NOT support provider, model, or base_url. I tried it — it returns success but doesn't change those fields. Earlier versions of this skill had it wrong.
You MUST edit ~/.hermes/cron/jobs.json directly:
import json
data = json.load(open('/Users/jinguo/.hermes/cron/jobs.json'))
for j in data['jobs']:
if j['id'].startswith('<job_id>'.replace('-','')[:8]):
j['provider'] = '<new_provider>'
j['model'] = '<new_model>'
j['base_url'] = '<new_base_url>'
break
with open('/Users/jinguo/.hermes/cron/jobs.json', 'w') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
Why direct edit is needed: yidong and the other 3 custom providers are per-job overrides. The base_url is captured into the job record at creation time and never re-read from config. If you only update provider without base_url, the gateway fails silently with last_run_at: null forever.
4. Backup + (optional) cronjob update to force gateway re-read
cp ~/.hermes/cron/jobs.json ~/.hermes/cron/jobs.json.bak.$(date +%s)
Then any no-op cronjob update (e.g. change deliver from local to origin) re-serializes the full record including the patched fields, forcing the gateway to re-evaluate on its next tick.
5. cronjob run to queue for next tick
Correction (2026-06-01): cronjob run does NOT actually fire the LLM agent. It only pushes next_run_at ~1 minute forward. The gateway scheduler is what actually fires LLM agents, and only on natural tick boundaries. If the new job's next_run_at is in the past, the gateway should pick it up on the next tick (usually within 1-2 minutes). If it doesn't, you need remove + create (fresh job_id, guaranteed pickup).
6. Verify with ground truth, NOT metadata
Correction (2026-06-01): do NOT trust last_run_at in jobs.json. The gateway updates this field only when cronjob run is called — NOT when the scheduler naturally fires the LLM agent. Real fire evidence (in priority order):
- git log in target repo —
cd ~/wiki && git log --since="5 min ago" --oneline for new ingest commits
- Heartbeat file mtime —
ls -la ~/wiki/heartbeat/<job>.last-run
- cron-status.log — LLM writes closeout lines here
- Target side effects — e.g.,
ls raw/rss-inbox/ | wc -l decreased
~/.hermes/cron/output/<id>/ — new <timestamp>.md files
Empirically validated: a new job (bb908e83c440 wechat-inbox-pipeline) ran successfully, ingested 12 articles, produced commits 3f51cf0c, 86be31f0, e9f8e53c — but last_run_at stayed null in jobs.json the whole time.
Full evidence hierarchy + validated misdiagnoses: see references/cron-fire-evidence.md in the cron-job-prompt-recovery skill.
Provider reference (4 custom + 2 built-in as of 2026-06-01)
| Name | base_url | Model | Notes |
|---|
minimax-cn | https://api.minimaxi.com/v1/text/chatcompletion_v2 | MiniMax-M3 | Default. NOT minimax.chat (returns reply field, different shape). NOT MiniMax-M2.7 (auxiliary_name; 2013 error). Use MiniMax-M3 with dot. |
deepseek | https://api.deepseek.com | deepseek-v4-pro | Built-in |
yidong | https://zhenze-huhehaote.cmecloud.cn/api/coding/v1 | cm-code-latest (rewritten to minimax-m2.5 by endpoint!) | Auth Bearer {key}. Response may have content: null — fall back to reasoning_content. |
maas | https://maas-coding-api.cn-huabei-1.xf-yun.com/v2 | astron-code-latest | Custom |
Qianfan.baidubce.com | https://qianfan.baidubce.com/v2/coding | glm-5 (also deepseek-v4-flash, kimi-k2.5) | Custom, multiple models per provider |
Ark.cn-beijing.volces.com | https://ark.cn-beijing.volces.com/api/coding/v3 | kimi-k2.6 | Custom |
For yidong probe code and quirks, see references/yidong-provider-verification.md.
Common pitfalls (updated 2026-06-01)
Don't restart Hermes
Earlier versions of this skill said "restart Hermes after migration". Don't. The gateway is a long-running process and Hermes' own restart tools (--replace) cache jobs.json on startup. A direct jobs.json edit + cronjob update no-op is enough. If something is truly broken, use the migration script's --dry-run to validate before destructive ops.
last_run_at doesn't prove firing
See "Verify with ground truth" above. Using last_run_at as fire evidence was the #1 misdiagnosis of the 2026-06-01 session — agent.log showed 0 successful runs but last_run_at was null (gateway writes null) while git log showed 4 successful ingest commits.
cronjob run doesn't force a fire
See Step 5. It pushes next_run_at forward and returns success. The actual LLM agent runs on the gateway's natural tick.
create may not persist base_url/model
When you call cronjob create with provider=yidong and model=cm-code-latest but no base_url arg, the resulting job record may have base_url: null and model: null. The gateway will then fail silently to fire. Workaround: after create, immediately follow with a direct jobs.json edit (Step 3) to populate all three fields, then a cronjob update no-op to force re-serialize.
cronjob update doesn't change provider
Already covered in Step 3. Don't waste a turn trying.
Verification Checklist
See also
cron-job-prompt-recovery — covers prompt extraction, gateway caching, ground-truth fire evidence (4 reference files in this skill's umbrella)
references/yidong-provider-verification.md (this skill) — yidong quirks, custom_providers list
scripts/migrate-cron-provider.py (this skill) — the validated end-to-end workflow
wiki-pipeline — for what the wiki LLM scoring expects from a provider