This skill should be used to fire an allowlisted cron manual-trigger on demand via POST /api/internal/trigger-cron, without SSH. Reads the secret read-only from Doppler, lists allowlisted events, supports optional event data and a dry-run.
Scanned 9/3/2026
Install to Claude Code
npx -y skills add jikig-ai/soleur --skill trigger-cron --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Trigger Cron?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/jikig-ai-trigger-cron)More formats (shields.io, HTML) on the badges page.
---
name: trigger-cron
description: "This skill should be used to fire an allowlisted cron manual-trigger on demand via POST /api/internal/trigger-cron, without SSH. Reads the secret read-only from Doppler, lists allowlisted events, supports optional event data and a dry-run."
---
# trigger-cron
Fires a `cron/<name>.manual-trigger` event on demand by POSTing to the internal
trigger route `POST /api/internal/trigger-cron` — the no-SSH replacement for the
on-host `inngest send` loopback path (#4734, #4742). Use this instead of SSH-ing
to the Hetzner box whenever a cron needs an on-demand run.
## When to use
- Re-running a probe/audit cron after fixing a secret (e.g. `cron/oauth-probe.manual-trigger`).
- Targeting a specific issue with the bug-fixer (`cron/bug-fixer.manual-trigger`
with `data: { issue_number: N }`).
- Any operator/agent "fire this cron now" need — see the runbooks
`knowledge-base/engineering/operations/runbooks/inngest-server.md` and
`oauth-probe-failure.md`.
The cron CRUD set: `soleur:schedule` (Create), `soleur:cron-list` (Read),
`soleur:cron-delete` (Delete), **`soleur:trigger-cron` (Run-now — this)**.
## How it works
The route authenticates a fail-closed shared secret
(`INNGEST_MANUAL_TRIGGER_SECRET`, Doppler-provisioned) via a `Bearer` header,
validates the event against the allowlist derived from `EXPECTED_CRON_FUNCTIONS`
(`apps/web-platform/server/inngest/cron-manifest.ts`), and dispatches the event
through the app's wired Inngest client. Route-controlled keys (`trigger`, `at`)
are stamped server-side and CANNOT be overridden by caller `data`.
## Usage
The wrapper script is [scripts/trigger.sh](./scripts/trigger.sh):
**Run this preflight first, and abort on a non-zero exit.** `trigger.sh` reads
`INNGEST_MANUAL_TRIGGER_SECRET` from Doppler at `-c prd` and fires a real production event,
so this is a secret gate in the sense of ADR-179 decision 2 — and until #7450 it carried no
env-var precondition at all, which made it **cheaper to exploit than the redaction gates that
issue was filed about**. A `:-` default resolves into the caller's working tree; on the review
path that tree is a contributor's PR, so the defaulted form of this path would run *their*
script with the operator's prod secret in reach. (The rejected literal is deliberately not
reproduced here: a corpus-wide body-grep cannot tell an explanation from an occurrence, so
quoting it would make this paragraph a residual site in the sweep that hunts it.)
```bash
[ -f "${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json" ] \
&& grep -q '"name"[[:space:]]*:[[:space:]]*"soleur"' "${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json" \
|| { echo "SOLEUR_TRIGGER_CRON_HALT reason=plugin-root-unverified root=[${CLAUDE_PLUGIN_ROOT}]"
echo "trigger-cron: cannot verify the Soleur plugin installation — stopping before any event fires." >&2
echo " Resolved plugin root: [${CLAUDE_PLUGIN_ROOT}]" >&2
echo " If that is EMPTY: no Soleur plugin is loaded in this session. Install it and start a NEW session — re-running here resolves the same empty root." >&2
echo " If it names a path: that path is not a Soleur install (a repo checkout is not an install). Run 'claude plugin update soleur', then RESTART Claude Code — plugin changes apply only on restart. If you installed with --scope project or --scope local, pass the same scope. Reinstall only if that does not clear it." >&2
echo " No event has been fired and no secret has been read." >&2
exit 2; }
TRIGGER="${CLAUDE_PLUGIN_ROOT}/skills/trigger-cron/scripts/trigger.sh"
[ -x "$TRIGGER" ] || { echo "SOLEUR_TRIGGER_CRON_HALT reason=producer-missing producer=scripts/trigger.sh"
echo "SOLEUR_TRIGGER_CRON_PRODUCER_MISSING producer=scripts/trigger.sh reason=absent-from-verified-root"
echo "trigger-cron: the plugin root verifies but does not carry trigger.sh — halt (fail closed)" >&2
echo " Your project is not at fault: the installed Soleur payload is incomplete or stale." >&2
echo " What to do: run 'claude plugin update soleur', then RESTART Claude Code (updates apply only on restart);" >&2
echo " if you installed with --scope project or --scope local, pass the same scope. If it still persists," >&2
echo " reinstall the plugin. Updating the MARKETPLACE alone does not update an INSTALLED plugin." >&2
echo " Do NOT let the agent run the reinstall — it mutates the plugin root under a live run." >&2
exit 2; }
```
With the preflight passed, `$TRIGGER` is the verified producer:
```bash
# List the allowlisted manual-trigger events (sourced from the cron manifest).
"${CLAUDE_PLUGIN_ROOT}/skills/trigger-cron/scripts/trigger.sh" --list
# Dry-run: print the curl without firing.
"${CLAUDE_PLUGIN_ROOT}/skills/trigger-cron/scripts/trigger.sh" \
--event cron/bug-fixer.manual-trigger --data '{"issue_number":4383}' --dry-run
# Fire (default config: prd).
"${CLAUDE_PLUGIN_ROOT}/skills/trigger-cron/scripts/trigger.sh" \
--event cron/workspace-sync-health.manual-trigger
```
### Flags
| Flag | Meaning |
|---|---|
| `--list` | Print the allowlisted events (from `cron-manifest.ts`) and exit. |
| `--event <name>` | The `cron/<name>.manual-trigger` event to fire (validated against the allowlist). |
| `--data '<json>'` | Optional per-cron `event.data` (must be a JSON object). Forwarded verbatim; the route stamps `trigger`/`at` over it. |
| `--config prd\|dev` | Doppler config to read the secret from (default `prd`). |
| `--dry-run` | Print the curl invocation without firing. |
## Sharp edges
- **Run from a worktree, never the bare-repo root.** `trigger.sh` locates the allowlist via the repo's `cron-manifest.ts` and fails on a bare repo (`not inside a git repo (cannot locate cron-manifest.ts)`). From a bare-root session, `cd` into any `.worktrees/<feature>/` first (or run the script by absolute path while CWD is inside a worktree) — the firing curl is server-side-allowlist-validated, so the worktree's local manifest version does not affect correctness. See `knowledge-base/project/learnings/integration-issues/2026-06-05-cloud-task-silence-per-producer-triage-and-handler-fallback.md`.
- **Read-only secret access.** The script reads `INNGEST_MANUAL_TRIGGER_SECRET`
via `doppler secrets get … --plain` only — it never writes or mutates the
secret, never echoes or logs it, and `unset`s the token immediately after the
`curl` call. The token is passed as the `curl` `Authorization` header argument
(same exposure profile as every other operator `curl` in the runbooks); the
`--dry-run` path prints the command form with `$TOKEN` unexpanded, so it never
mints or reveals the secret.
- **The route is a dumb forwarder.** Per-cron field validation (e.g.
`issue_number` positive-integer) lives in each cron, not the route. A bad
`--data` shape that the route accepts (any plain object) may still be rejected
cron-side with a Sentry fallback.
- **Allowlist is the blast-radius bound.** A non-allowlisted event returns 400.
The allowlist auto-tracks `cron-*.ts` via `EXPECTED_CRON_FUNCTIONS` — there is
no second hand-maintained list.
- **Mutating crons spend budget / open PRs / post publicly.** `cron/bug-fixer.manual-trigger`
opens a PR; content/competitive/growth crons spend API budget;
`cron/weekly-release-digest.manual-trigger` POSTS a digest to the public
community `#releases` channel (no dry-run mode — every fire publishes). Use
`--dry-run` first; never smoke-test a mutating cron post-merge — use a
data-free, side-effect-light event like
`cron/workspace-sync-health.manual-trigger`. (The digest's one-time
post-merge verification fire per #5080 plan Phase 6.2 was the sanctioned
launch post, not a precedent.)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!