File a locally-captured, already-redacted Gini crash report as a GitHub issue, with the user's consent. Reads the pending crash queue and delegates the actual filing to the github-issues skill.
68
83%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
File a Gini crash that was captured locally — and redacted at capture time —
as a GitHub issue, but only after the user says yes. This skill is the
consent-aware orchestration layer on top of the github-issues skill: it
reads the pending crash queue, summarizes it for the user, and on a "yes"
delegates the actual gh filing to github-issues. It never invents crash
data and never re-reads raw logs.
default
instance, Gini posted a chat message asking whether to file captured
crash(es). The user replied in that thread — act on their answer here.github-issues skill directly; this skill only files queued crashes.github-issues and write the body yourself.runtime.jsonl, secrets,
full logs) → out of scope. The queued report is the trust boundary; never
go around it.Approved terminal_exec runs with the working directory set to the
workspace root — not this skill's directory and not the crash
directory. Address the queue by ABSOLUTE path. Define it once and reuse it:
QUEUE="${GINI_STATE_ROOT:-$HOME/.gini}/crash-reports"Layout:
$QUEUE/pending/*.json — captured crashes awaiting a consent decision.$QUEUE/filed/ — reports the user agreed to file (moved here after a
successful gh filing).$QUEUE/dismissed/ — reports the user declined.Each pending file is one JSON crash report with this shape (field names are exact):
{ instance, source, supervisor, fingerprint, at,
error: { name, message, stack },
sysInfo: { platform, arch, nodeVersion, giniCommit },
logTail: [ { at, message }, ... ] }The report is ALREADY redacted at capture time. Secrets, tokens, bearer
headers, and the data payload of every log line were stripped before the
file was written. Pass these fields through to the issue as-is. Do NOT
re-fetch raw logs, runtime.jsonl, secrets, or anything outside $QUEUE —
this report is published to GitHub, so the queued JSON is the only data you
are allowed to use.
Read the queue and summarize each distinct crash for the user. Always quote the file path so step 5 can resolve it later.
QUEUE="${GINI_STATE_ROOT:-$HOME/.gini}/crash-reports"
shopt -s nullglob
pending=("$QUEUE/pending"/*.json)
if [ ${#pending[@]} -eq 0 ]; then
echo "No pending crash reports."
else
for f in "${pending[@]}"; do
jq -r '"\(input_filename)
source: \(.source)
error: \(.error.name): \(.error.message)
at: \(.at)
fingerprint: \(.fingerprint)
"' "$f"
done
fiIf the queue is empty, tell the user there's nothing to report and stop — do not file anything.
Multiple pending files can share one fingerprint (the same crash
recurring). Group by fingerprint — you file one issue per distinct
fingerprint (see step 3), not one per file.
The restart consent flow already asked the user the "want me to file this?" question, so on this turn you are acting on their reply:
File ONLY the fingerprints the consent named. The consent message in
this conversation listed the specific crash fingerprint(s) it covers in full
(e.g. The specific crash fingerprint(s) this consent covers (match these EXACTLY): <64-hex-fingerprint>, ...). A new crash can land in
$QUEUE/pending between the ask and the user's "yes"; filing it would be
unconsented. So when you list the queue in step 1, match each pending file's
.fingerprint against the fingerprint(s) named in the consent message by
EXACT, full-string equality (not by prefix — a short prefix could
misattribute a later same-prefix crash to this consent). Act on those files
only. You may display a shortened form to the user as cosmetic sugar, but the
file/dedup decision must use the exact fingerprint. Leave any pending file
whose fingerprint was not named where it is; a later consent flow will offer
it.
The one exception: if the user explicitly asks to file all pending crashes ("file all of them", "report everything in the queue"), then act on every pending file.
If you were invoked manually (the user asked directly, without the consent message having been posted), ask first: summarize the pending crash(es) from step 1 and ask whether to file them. Wait for the answer before doing anything in step 3. Either way, confirm before filing.
Target repository. A Gini crash is filed against the canonical Gini repo
— Open-Curiosity/gini-agent — NOT whatever git remote the current workspace
happens to have (the agent's working directory is a sandbox, not a Gini
checkout). Pass --repo Open-Curiosity/gini-agent to every gh call below.
Do not duplicate gh mechanics here. Load the github-issues skill and
follow its gh path — it handles installing gh, the interactive
gh auth login degrade, idempotent label creation, and create/search:
read_skill name='github-issues'Then, once per distinct fingerprint that the consent named (skip any
pending file whose .fingerprint does not EXACTLY equal one named in the
consent message, unless the user explicitly asked to file all):
Dedup first. Search for an existing open crash issue with the same
fingerprint (per github-issues Listing and Searching). The fingerprint
is embedded in the body, so search for it:
gh issue list --repo Open-Curiosity/gini-agent --state open --label "gini-crash" \
--search "<fingerprint> in:body" --json number,titleIf an open issue already carries this fingerprint, comment on it
instead of opening a duplicate (per github-issues Comments) — note the
recurrence and its at timestamp — then go to step 5 for the matching
file(s). Quote the issue number verbatim (#N) when you tell the user.
Ensure the label exists (idempotently, per github-issues Labels —
GitHub returns a generic 422 for an unknown label rather than creating
it):
gh label list --repo Open-Curiosity/gini-agent --search "gini-crash" | grep -q gini-crash \
|| gh label create "gini-crash" --repo Open-Curiosity/gini-agent \
--description "Automatically captured Gini crash" --color B60205Create the issue. Title is [gini-crash] <source>: <error.name>.
Assemble the body only from the redacted report JSON — the summary,
source, fingerprint, the stack, sysInfo, and the redacted log tail.
Build it with jq straight from the queued file so nothing un-redacted
can sneak in:
QUEUE="${GINI_STATE_ROOT:-$HOME/.gini}/crash-reports"
f="$QUEUE/pending/<the-report>.json"
title=$(jq -r '"[gini-crash] \(.source): \(.error.name)"' "$f")
body=$(jq -r '
"## Summary\n" +
"\(.error.name): \(.error.message)\n\n" +
"- **source:** \(.source)\n" +
"- **instance:** \(.instance)\n" +
"- **when:** \(.at)\n" +
"- **fingerprint:** `\(.fingerprint)`\n\n" +
"## Stack\n```\n\(.error.stack)\n```\n\n" +
"## System\n" +
"- platform: \(.sysInfo.platform) (\(.sysInfo.arch))\n" +
"- node: \(.sysInfo.nodeVersion)\n" +
"- gini commit: \(.sysInfo.giniCommit // "unknown")\n\n" +
"## Recent log tail (redacted)\n```\n" +
((.logTail // []) | map("\(.at // "") \(.message // "")") | join("\n")) +
"\n```\n"
' "$f")
gh issue create --repo Open-Curiosity/gini-agent --label "gini-crash" --title "$title" --body "$body"Then resolve the file(s) for this fingerprint (step 5). Quote the new
issue number (#N) when you confirm to the user.
The fingerprint carried in the body is what makes the dedup search in
step 1 work for the next recurrence — always include it.
Filing needs gh signed in. Defer to github-issues' Setup for the
mechanics — it installs gh itself if missing, but the sign-in is
interactive. If gh auth status is not authenticated, tell the user in
one line and stop; pick up once they confirm:
Run
gh auth loginin your terminal to sign in, then I can file the crash.
Re-run gh auth status to verify before continuing to step 3.
If the user declines to authenticate, STOP and leave the report in
$QUEUE/pending/ — do NOT dismiss it. Leaving it pending lets the consent
flow offer it again on a later restart. Dismissing is only for an explicit
"no, don't report this".
Resolve each report file only after its decision is final. These moves
mirror the runtime's resolvePendingReport; create the sibling dir first so
the move never fails.
After a successful file (or a dedup comment) for a fingerprint, move every
pending file with that fingerprint into filed/:
QUEUE="${GINI_STATE_ROOT:-$HOME/.gini}/crash-reports"
mkdir -p "$QUEUE/filed"
mv "$QUEUE/pending/<the-report>.json" "$QUEUE/filed/"On an explicit "no", move the report(s) into dismissed/:
QUEUE="${GINI_STATE_ROOT:-$HOME/.gini}/crash-reports"
mkdir -p "$QUEUE/dismissed"
mv "$QUEUE/pending/<the-report>.json" "$QUEUE/dismissed/"Never delete a pending report any other way: filed → filed/,
declined → dismissed/, and gh-not-authed / user-defers → leave it in
pending/.
runtime.jsonl, no secrets, no logs
outside $QUEUE. The issue body comes only from the queued JSON.${GINI_STATE_ROOT:-$HOME/.gini}/crash-reports). terminal_exec runs
from the workspace root, so relative paths will miss the queue.gh use its stored credential (per github-issues).gini-crash issues for the fingerprint first; comment on a match rather
than opening a duplicate..fingerprint by EXACT, full-string equality and act on those files only.
A crash that landed after the ask is unconsented — leave it pending. The
sole exception is an explicit "file all" from the user.#N) in replies so GitHub deeplinks
resolve them.6c5d85e
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.