| name | gini-bug-report |
| description | 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. |
| license | MIT |
| compatibility | Runs on the default, launchd-supervised Gini instance. Needs `gh` (via the github-issues skill) to actually file. Reads the local crash queue under ${GINI_STATE_ROOT:-$HOME/.gini}/crash-reports. |
| allowed-tools | read_skill terminal_exec |
| metadata | {"gini":{"version":"1.0.0","author":"Gini","category":"dev","platforms":["macos","linux"],"prerequisites":{"commands":["gh","git","jq"]}}} |
Gini Bug Report
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.
When To Use
- The crash-report consent flow invoked you: on restart of the
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.
- The user explicitly asks to report a captured crash ("file that crash",
"report the crash you found").
When NOT to Use
- General GitHub issue work (create/search/triage/label/close) → use the
github-issues skill directly; this skill only files queued crashes.
- A non-crash bug report, feature request, or any issue not already sitting
in the crash queue → use
github-issues and write the body yourself.
- Anything that would need un-redacted data (raw
runtime.jsonl, secrets,
full logs) → out of scope. The queued report is the trust boundary; never
go around it.
- Proactively scanning the queue or nagging the user → don't. Only act when
invoked by the consent flow or an explicit request.
The crash queue
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.
1. List pending crashes
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
fi
If 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.
2. Confirm intent — and scope to the asked fingerprints
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:
- The user said yes (or "report it", "go ahead") → proceed to step 3.
- The user said no (or "don't", "skip it") → file nothing; mark the
report(s) dismissed (step 5).
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.
3. On "yes" — file via github-issues
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,title
If 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 B60205
-
Create 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.
4. If gh is not authenticated
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 login in 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".
5. Mark resolved
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/.
Rules
- The queued report is already redacted. Never attach, re-derive, or
re-fetch un-redacted data — no raw
runtime.jsonl, no secrets, no logs
outside $QUEUE. The issue body comes only from the queued JSON.
- Always address the queue by ABSOLUTE path
(
${GINI_STATE_ROOT:-$HOME/.gini}/crash-reports). terminal_exec runs
from the workspace root, so relative paths will miss the queue.
- Never put a token on a command line or in an issue/comment body. Let
gh use its stored credential (per github-issues).
- One issue per distinct fingerprint. Search the existing open
gini-crash issues for the fingerprint first; comment on a match rather
than opening a duplicate.
- File only the fingerprints the consent named. The consent message
lists the full crash fingerprint(s) it covers; match each pending file's
.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.
- Confirm before filing. Act on the user's consent-flow reply, or — if
invoked manually — ask first and wait.
- Quote issue numbers verbatim (
#N) in replies so GitHub deeplinks
resolve them.