The buglog was one 27K-line JSON file whose fields covered only symptom,
root cause and fix, as free text: 2% of entries named the code version a
bug was found on, 12% the build profile, 36% a way to reproduce, and 58% of
the file was OpenWolf hook auto-detections ("Missing guard clause",
"Function not marked async" on .md files).
- .claude/skills/buglog/bug-record.md: the record standard. Eight
sections (Summary as the title, Description, Environment, Reproduction;
Root cause, Fix, Verification, References once fixed), each with the
content it must contain -- e.g. Environment needs the OS, the libc the
failing binary loads (from its ELF interpreter: glibc 2.42 in the nix
shell vs 2.40 on the host), toolchain, build profile and commit.
- .wolf/bugs/<id>.md: 500 records migrated from buglog.json; every field
verified to round-trip. They carry legacy: true and an explicit "not
recorded" where the old log had no such field; component derived from
file paths, severity from keywords (else unknown), fix_commits from
hashes in status_note/fix that git resolves (91 of 463 fixed).
- .wolf/buglog-archive.json: the 710 hook auto-detections, verbatim.
- .wolf/buglog.json is now an index generated from the records, keeping
the fields OpenWolf's hooks and `openwolf bug search` read.
- scripts/buglog.py rewritten: new (template + Environment collected from
the machine and --binary), check, status (refuses a status the record
doesn't support; fixed needs Root cause, Fix, Verification and
--commit hashes git resolves), note, recur, set, show, list, triage,
env, index. 29 unittest cases in scripts/tests/test_buglog.py.
- scripts/git-hooks/pre-commit runs `check --staged`: incomplete staged
records, a legacy record hand-changed to fixed without the fix-time
sections, a new record claiming legacy: true, or a stale index refuse
the commit.
- scripts/gitea-sync.py reads the records: issue body = the record's
sections plus linked fix commits (origin remote, GITEA_COMMIT_REPO to
override), and a body hash so an issue's text is refreshed when the
record changes.
- bug-1866 rewritten to the full standard as the worked example.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
4.5 KiB
| id | status | status_updated | status_note | component | severity | timestamp | last_seen | occurrences | files | tags | related | legacy | gitea_issue | gitea_synced_status | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| bug-1742 | fixed | 2026-09-24 | apiserver | unknown | 2026-09-01T04:10:00Z | 2026-09-19T09:00:00Z | 1 |
|
|
true | 45 | fixed |
bug-1742: Gitea issue #45: xcecli jobs kill <jobid> fails for every job-control action…
Description
Gitea issue #45: xcecli jobs kill <jobid> fails for every job-control action (kill/stop/resume/checkpoint) with request to /xce/v1/jobs/<id>/control failed: [] -- an empty error body, not a normal ReturnError JSON payload. Reproduced by static analysis of the full CLI->apiserver call path (job ID conversion verified correct by hand: MakeJobID(1,0,1788186846,0,1) == 73932643848159233, matching the URL in the report, so the numeric-vs-string ID mismatch in the bug report is a red herring).
Environment
Not recorded (migrated from buglog.json).
Reproduction
Not recorded (migrated from buglog.json).
Root cause
models.JobInfo declares JobStatus and ArrayIdx as plain Go int (jobs.go:489,493), not int64. store.StructToOrderMap builds jobinfo.Data via reflection (val.Field(i).Interface()), which preserves the field's concrete runtime type -- so Data["job_status"] and Data["array_idx"] are boxed as int, never int64. validateJobControl's guard at jobs.go:3014 (if jobStatus, ok := rawStatus.(int64); ok) therefore ALWAYS evaluates false for every job, unconditionally routing into the else error branch (3052-3060). That branch immediately does an UNCHECKED single-return assertion at jobs.go:3057, int(jobinfo.Data["array_idx"].(int64)) -- since array_idx is actually int, this panics with interface conversion: interface {} is int, not int64 on every single job-control call, regardless of job status, action, or whether the job is an array member (the reporter's job happening to be an array index is incidental; the same unsafe .(int64) pattern is repeated at jobs.go:3024/3031/3040/3048/3066, and in foundJob at 3081-3082 for the array-index-lookup path). Gin's default Recovery() middleware (routers.go:19) catches the panic and returns an empty 500 body with no JSON, which is exactly what the xcecli client's error-fallback path (request.go:219, HandleErrorResponse) renders as "%s failed: [%s]" with an empty httpResp.String() -- producing the reported failed: [] message. Confirmed the type mismatch directly by reading the struct declarations; confirmed the panic site's code by reading validateJobControl in full; not yet confirmed via a live repro/server-log stack trace (static analysis only this session).
Fix
FIXED (2026-09-01). validateJobControl (jobs.go:2997) now converts job_status and array_idx via utils.IntOrInt64() (already used elsewhere for this exact int/int64 mismatch, e.g. processJobData) instead of bare .(int64) assertions -- a failed conversion is now reported as a normal failedJobs entry with a descriptive msg instead of panicking. foundJob (jobs.go:3077) gets the same fix for its array-index lookup path. TDD: added TestValidateJobControl_HandlesRealJobInfoTypes (builds a real models.JobInfo and converts it via store.StructToOrderMap(), the same reflection path production uses, then asserts validateJobControl doesn't panic and returns a correctly-typed success job -- this is the test that would have caught the original bug, since it exercises the real int/int64 boundary instead of hand-built int64 map literals), TestValidateJobControl_RejectsNonKillableStatus, and TestFoundJob_MatchesRealArrayIdxType (jobs_test.go). Confirmed red (panic at jobs.go:3057, "interface conversion: interface {} is int, not int64") before the fix, green after. Full apiserver suite (store/utils/controllers/backend/backend/auth) passes with zero regressions.
Verification
Not recorded (migrated from buglog.json).
References
- Gitea: #45