gastownhall/beads · error
add comment to %s: %w
Error message
add comment to %s: %w
What it means
Wraps the INSERT of a new comment row (under its content-derived id) in InsertDerivedComment after the existence probe found no duplicate. Failure means the row couldn't be written: duplicate derived id, missing table/columns, constraint violation, or the transaction already aborted. Callers addIssueCommentInTx and PersistComments propagate it, failing the comment add.
Source
Thrown at internal/storage/issueops/derivedid.go:233
func InsertDerivedComment(ctx context.Context, tx DBTX, table, issueID, author, text, createdAt string) (id string, existed bool, err error) {
err = tx.QueryRowContext(ctx, fmt.Sprintf(`
SELECT id FROM %s
WHERE issue_id = ? AND author = ? AND text = ? AND created_at = ?
ORDER BY id LIMIT 1`, table),
issueID, author, text, createdAt).Scan(&id)
if err == nil {
return id, true, nil
}
if err != sql.ErrNoRows {
return "", false, fmt.Errorf("check comment existence in %s: %w", table, err)
}
digest := rowid.Digest([]sql.NullString{str(issueID), str(author), str(text), str(createdAt)})
id = rowid.New(table, 0, digest)
if _, err := tx.ExecContext(ctx, fmt.Sprintf(`
INSERT INTO %s (id, issue_id, author, text, created_at)
VALUES (?, ?, ?, ?, ?)`, table),
id, issueID, author, text, createdAt); err != nil {
return "", false, fmt.Errorf("add comment to %s: %w", table, err)
}
return id, false, nil
}
// InsertDerivedCompactionSnapshot inserts a compaction_snapshots row under
// its content-derived id, with the same ordinal discipline as events. Two
// clones compacting the same issue at the same tier in the same second
// produce byte-identical snapshots and therefore the same id.
func InsertDerivedCompactionSnapshot(ctx context.Context, tx DBTX, issueID string, level int, snapshotJSON []byte, createdAt string) error {
if createdAt == "" {
createdAt = NowAuxTime()
}
snap := string(snapshotJSON)
digest := rowid.Digest([]sql.NullString{
str(issueID), str(fmt.Sprintf("%d", level)), str(snap), str(createdAt),
})
taken := make(map[string]bool)
rows, err := tx.QueryContext(ctx, `View on GitHub (pinned to 71377f2769)
Solutions
- Retry in a fresh transaction — the next existence probe will find the winner's row and dedup instead of inserting.
- Serialize comment writes per issue (avoid racing processes against the same Dolt DB).
- Run migrations to restore the expected comments-table schema.
- Inspect the wrapped error: duplicate-key implies benign race; unknown-column implies schema drift.
Example fix
// before: naive concurrent comment add
id, created, err := InsertDerivedComment(ctx, tx, table, issueID, author, text, createdAt)
// after: retry once on failure; dedup probe wins on second pass
id, created, err := InsertDerivedComment(ctx, tx, table, issueID, author, text, createdAt)
if err != nil {
tx2 := beginFresh()
id, created, err = InsertDerivedComment(ctx, tx2, table, issueID, author, text, createdAt)
} Defensive patterns
Strategy: retry
Validate before calling
cols, err := tableColumns(tx, table)
if err != nil { return err }
if len(cols) != 5 {
return fmt.Errorf("%s expects 5 columns (id, issue_id, author, text, created_at), got %d", table, len(cols))
}
if err := ctx.Err(); err != nil { return err } Type guard
func isDuplicateKeyErr(err error) bool {
var me *mysql.MySQLError
return errors.As(err, &me) && me.Number == 1062
} Try / catch
id, created, err := InsertDerivedComment(ctx, tx, table, issueID, author, text, createdAt)
if err != nil {
if isDuplicateKeyErr(err) || isTransient(err) {
return insertCommentFreshTx(ctx, table, issueID, author, text, createdAt)
}
return err
} Prevention
- Serialize comment writes per issue across processes.
- Retry once in a fresh transaction; the dedup probe resolves races.
- Keep import transactions short-lived to avoid server-side kills.
- Verify schema post-restore before running comment imports.
When it happens
Trigger: Two concurrent writers compute the same content-derived comment id; comments table schema drifted (missing author/text/created_at columns); transaction killed before the INSERT.
Common situations: Concurrent `bd comment` invocations or parallel import workers inserting the same comment; version/schema mismatch; long imports whose transactions get reaped server-side.
Related errors
- record event in %s: %w
- read newest comment time from %s: %w
- check comment existence in %s: %w
- load wisp comments: %w
- read comments for %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/b80380b7fbfd48fe.
Report an issue: GitHub.