From f59fbe417578b3e47c662fc179e3f37aa0174f98 Mon Sep 17 00:00:00 2001 From: Acho Arnold Ewin Date: Sun, 19 Jul 2026 15:25:40 +0300 Subject: [PATCH 1/7] docs: design formatting helper API Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1601a53e-b461-4419-af08-50a1839599b5 --- ...2026-07-19-formatting-helper-api-design.md | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-19-formatting-helper-api-design.md diff --git a/docs/superpowers/specs/2026-07-19-formatting-helper-api-design.md b/docs/superpowers/specs/2026-07-19-formatting-helper-api-design.md new file mode 100644 index 0000000..a111cf4 --- /dev/null +++ b/docs/superpowers/specs/2026-07-19-formatting-helper-api-design.md @@ -0,0 +1,54 @@ +# Formatting Helper API Design + +## Goal + +Add GoLand-recognizable formatting APIs without changing the behavior of the +existing public API or the stack frame recorded for callers. + +## Public API + +Add these functions: + +```go +func NewErrorf(format string, args ...any) error +func Propagatef(err error, format string, args ...any) error +func NewErrorWithCodef(code ErrorCode, format string, args ...any) error +func PropagateWithCodef(err error, code ErrorCode, format string, args ...any) error +func NewMessageWithCodef(code ErrorCode, format string, args ...any) error +``` + +Each function has the same semantics as its existing counterpart. Existing +functions remain unchanged and continue to accept formatting arguments for +backward compatibility. + +## Implementation + +The four stack-capturing functions call `create` directly. They must not +delegate through another public function because `create` uses +`runtime.Caller(2)`, and an extra wrapper frame would change the recorded file, +function, and line. + +`Propagatef` and `PropagateWithCodef` return `nil` for a nil cause. +`Propagatef` inherits an existing stacktrace code, while +`PropagateWithCodef` applies the supplied code. `NewMessageWithCodef` creates a +message-only coded error without caller information. + +## Documentation + +Add Go doc comments for all new exported functions. Update README signatures +and interpolated examples to use the `*f` variants, while documenting that the +original APIs remain supported. + +## Tests + +Add black-box coverage for: + +1. String interpolation in all five new functions. +2. Nil behavior for both propagation functions. +3. Code inheritance and explicit code assignment. +4. Accurate caller attribution for stack-capturing functions. + +## Compatibility + +This is additive. No existing symbol, signature, formatting behavior, error +chain behavior, or error-code behavior changes. From c1ffd950be75535b571a15ee1eff519e38c24c20 Mon Sep 17 00:00:00 2001 From: Acho Arnold Ewin Date: Sun, 19 Jul 2026 15:32:18 +0300 Subject: [PATCH 2/7] docs: plan formatting helper API Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1601a53e-b461-4419-af08-50a1839599b5 --- .../plans/2026-07-19-formatting-helper-api.md | 327 ++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-19-formatting-helper-api.md diff --git a/docs/superpowers/plans/2026-07-19-formatting-helper-api.md b/docs/superpowers/plans/2026-07-19-formatting-helper-api.md new file mode 100644 index 0000000..9f4495d --- /dev/null +++ b/docs/superpowers/plans/2026-07-19-formatting-helper-api.md @@ -0,0 +1,327 @@ +# Formatting Helper API Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add `*f` formatting helpers that GoLand recognizes while preserving the existing API and exact stacktrace behavior. + +**Architecture:** Add five public sibling functions beside their existing counterparts in `stacktrace.go`. Stack-capturing helpers call `create` directly to preserve `runtime.Caller(2)` attribution; tests exercise the public API and verify messages, codes, nil propagation, and caller names. + +**Tech Stack:** Go 1.18+, standard library `fmt`/`errors`, `testify/assert` + +## Global Constraints + +- Keep every existing public function and its formatting behavior backward-compatible. +- Add exactly `NewErrorf`, `Propagatef`, `NewErrorWithCodef`, `PropagateWithCodef`, and `NewMessageWithCodef`. +- Preserve direct call depth between every stack-capturing public helper and `create`. +- Preserve nil propagation and error-code inheritance/override semantics. +- Keep tests in the external `stacktrace_test` package. + +--- + +### Task 1: Add formatting helper APIs + +**Files:** +- Modify: `functions_for_test.go` +- Modify: `stacktrace_test.go` +- Modify: `stacktrace.go` + +**Interfaces:** +- Consumes: existing `create(err error, code ErrorCode, format string, args ...any) error`, `NoCode`, and private `stacktrace` +- Produces: + - `NewErrorf(format string, args ...any) error` + - `Propagatef(err error, format string, args ...any) error` + - `NewErrorWithCodef(code ErrorCode, format string, args ...any) error` + - `PropagateWithCodef(err error, code ErrorCode, format string, args ...any) error` + - `NewMessageWithCodef(code ErrorCode, format string, args ...any) error` + +- [ ] **Step 1: Add public call-site fixtures** + +Append these fixtures to `functions_for_test.go` so existing exact line assertions do not move: + +```go +func newErrorfAtCallSite() error { + return stacktrace.NewErrorf("new %d", 7) +} + +func propagatefAtCallSite(err error) error { + return stacktrace.Propagatef(err, "propagate %d", 7) +} + +func newErrorWithCodefAtCallSite() error { + return stacktrace.NewErrorWithCodef(EcodeInvalidVillain, "coded %d", 7) +} + +func propagateWithCodefAtCallSite(err error) error { + return stacktrace.PropagateWithCodef(err, EcodeNotFastEnough, "coded propagate %d", 7) +} +``` + +- [ ] **Step 2: Write failing behavior tests** + +Append these tests to `stacktrace_test.go`: + +```go +func TestFormattingHelpers(t *testing.T) { + root := errors.New("root") + + tests := []struct { + name string + err error + expected string + code stacktrace.ErrorCode + }{ + { + name: "new error", + err: stacktrace.NewErrorf("new %d", 7), + expected: "new 7", + code: stacktrace.NoCode, + }, + { + name: "propagate", + err: stacktrace.Propagatef(root, "propagate %d", 7), + expected: "propagate 7: root", + code: stacktrace.NoCode, + }, + { + name: "new error with code", + err: stacktrace.NewErrorWithCodef(EcodeInvalidVillain, "coded %d", 7), + expected: "coded 7", + code: EcodeInvalidVillain, + }, + { + name: "propagate with code", + err: stacktrace.PropagateWithCodef(root, EcodeNotFastEnough, "coded propagate %d", 7), + expected: "coded propagate 7: root", + code: EcodeNotFastEnough, + }, + { + name: "new message with code", + err: stacktrace.NewMessageWithCodef(EcodeInvalidVillain, "message %d", 7), + expected: "message 7", + code: EcodeInvalidVillain, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.expected, fmt.Sprintf("%#s", test.err)) + assert.Equal(t, test.code, stacktrace.GetCode(test.err)) + }) + } +} + +func TestFormattingHelpersPreserveCode(t *testing.T) { + err := stacktrace.NewErrorWithCodef(EcodeInvalidVillain, "inner %d", 7) + err = stacktrace.Propagatef(err, "outer %d", 8) + + assert.Equal(t, EcodeInvalidVillain, stacktrace.GetCode(err)) + assert.Equal(t, "outer 8: inner 7", fmt.Sprintf("%#s", err)) +} + +func TestFormattingPropagationNil(t *testing.T) { + assert.Nil(t, stacktrace.Propagatef(nil, "propagate %d", 7)) + assert.Nil(t, stacktrace.PropagateWithCodef(nil, EcodeInvalidVillain, "propagate %d", 7)) +} + +func TestFormattingHelpersCaptureCaller(t *testing.T) { + useFixturePaths(t) + root := errors.New("root") + + tests := []struct { + name string + err error + function string + }{ + {name: "new error", err: newErrorfAtCallSite(), function: "newErrorfAtCallSite"}, + {name: "propagate", err: propagatefAtCallSite(root), function: "propagatefAtCallSite"}, + {name: "new error with code", err: newErrorWithCodefAtCallSite(), function: "newErrorWithCodefAtCallSite"}, + {name: "propagate with code", err: propagateWithCodefAtCallSite(root), function: "propagateWithCodefAtCallSite"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + trace := fmt.Sprintf("%+s", test.err) + assert.Contains(t, trace, fixturePath("functions_for_test.go")) + assert.Contains(t, trace, "("+test.function+")") + }) + } +} +``` + +- [ ] **Step 3: Run the targeted tests and verify they fail** + +Run: + +```bash +go test -run '^TestFormatting' . +``` + +Expected: build failure reporting undefined `stacktrace.NewErrorf`, `stacktrace.Propagatef`, `stacktrace.NewErrorWithCodef`, `stacktrace.PropagateWithCodef`, and `stacktrace.NewMessageWithCodef`. + +- [ ] **Step 4: Implement direct formatting siblings** + +Add each function immediately after its existing counterpart in `stacktrace.go`: + +```go +// NewErrorf is the formatting variant of NewError. +func NewErrorf(format string, args ...any) error { + return create(nil, NoCode, format, args...) +} + +// Propagatef is the formatting variant of Propagate. +func Propagatef(err error, format string, args ...any) error { + if err == nil { + return nil + } + return create(err, NoCode, format, args...) +} + +// NewErrorWithCodef is the formatting variant of NewErrorWithCode. +func NewErrorWithCodef(code ErrorCode, format string, args ...any) error { + return create(nil, code, format, args...) +} + +// PropagateWithCodef is the formatting variant of PropagateWithCode. +func PropagateWithCodef(err error, code ErrorCode, format string, args ...any) error { + if err == nil { + return nil + } + return create(err, code, format, args...) +} + +// NewMessageWithCodef is the formatting variant of NewMessageWithCode. +func NewMessageWithCodef(code ErrorCode, format string, args ...any) error { + return &stacktrace{ + message: fmt.Sprintf(format, args...), + code: code, + } +} +``` + +- [ ] **Step 5: Format and run targeted tests** + +Run: + +```bash +gofmt -w stacktrace.go stacktrace_test.go functions_for_test.go +go test -run '^TestFormatting' . +``` + +Expected: all `TestFormatting*` tests pass. + +- [ ] **Step 6: Run package regression tests** + +Run: + +```bash +go test -race -cover ./... +``` + +Expected: all packages pass under the race detector. + +- [ ] **Step 7: Commit the API and tests** + +```bash +git add stacktrace.go stacktrace_test.go functions_for_test.go +git commit -m "feat: add formatting helper APIs" \ + -m "Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" \ + -m "Copilot-Session: 1601a53e-b461-4419-af08-50a1839599b5" +``` + +### Task 2: Document formatting helper usage + +**Files:** +- Modify: `README.md` + +**Interfaces:** +- Consumes: the five `*f` functions added in Task 1 +- Produces: user guidance that distinguishes formatting calls from static-message calls + +- [ ] **Step 1: Update interpolated README examples** + +Change calls with interpolation arguments to their formatting variants: + +```go +return stacktrace.Propagatef(err, "Failed to write %v to %s", ent, path) +``` + +```go +return nil, stacktrace.Propagatef(err, "Failed to process %v", arg) +``` + +```go +return stacktrace.NewErrorf("Expected %v to be okay", arg) +``` + +```go +return stacktrace.NewErrorf("timed out after %d attempts", attempts) +``` + +Keep static-message calls such as `Propagate(err, "")` and +`Propagate(err, "Failed to create base directory")` unchanged. + +- [ ] **Step 2: Document all five signatures** + +Change the primary formatted signatures and code variants to: + +```markdown +#### `stacktrace.Propagatef(err error, format string, args ...any) error` + +`Propagatef` wraps an error with a formatted message and line number +information. Use `Propagate` when the message has no formatting arguments. +Both functions return `nil` when `err` is nil. +``` + +```markdown +#### `stacktrace.NewErrorf(format string, args ...any) error` + +`NewErrorf` is a drop-in replacement for `fmt.Errorf` that includes line +number information. Use `NewError` when the message has no formatting +arguments. +``` + +```markdown +#### `stacktrace.PropagateWithCodef(err error, code ErrorCode, format string, args ...any) error` + +#### `stacktrace.NewErrorWithCodef(code ErrorCode, format string, args ...any) error` + +#### `stacktrace.NewMessageWithCodef(code ErrorCode, format string, args ...any) error` +``` + +State that the `*f` code variants format their messages like `fmt.Sprintf`, +that their counterparts without `f` remain supported for compatibility, and +that `PropagateWithCodef` retains nil propagation. + +- [ ] **Step 3: Verify README API names and fences** + +Run: + +```bash +rg -n 'Propagatef|NewErrorf|WithCodef' README.md +pre-commit run --all-files +``` + +Expected: every interpolated stacktrace example uses a `*f` function, all five +new names appear in the function documentation, and all pre-commit hooks pass. + +- [ ] **Step 4: Run the final quality gate** + +Run: + +```bash +go test -race -cover ./... +go vet ./... +golangci-lint fmt --diff +golangci-lint run +``` + +Expected: every command exits successfully with no findings. + +- [ ] **Step 5: Commit the documentation** + +```bash +git add README.md +git commit -m "docs: explain formatting helper APIs" \ + -m "Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" \ + -m "Copilot-Session: 1601a53e-b461-4419-af08-50a1839599b5" +``` From 29e510130c3d527ec8efe99650a8d9d73ad12804 Mon Sep 17 00:00:00 2001 From: Acho Arnold Ewin Date: Sun, 19 Jul 2026 15:34:14 +0300 Subject: [PATCH 3/7] feat: add formatting helper APIs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1601a53e-b461-4419-af08-50a1839599b5 --- functions_for_test.go | 16 ++++++++ stacktrace.go | 46 +++++++++++++++++++++++ stacktrace_test.go | 86 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) diff --git a/functions_for_test.go b/functions_for_test.go index aa4a794..45d4a97 100644 --- a/functions_for_test.go +++ b/functions_for_test.go @@ -51,3 +51,19 @@ func doClosure(err error) error { return stacktrace.Propagate(err, "so closed") }() } + +func newErrorfAtCallSite() error { + return stacktrace.NewErrorf("new %d", 7) +} + +func propagatefAtCallSite(err error) error { + return stacktrace.Propagatef(err, "propagate %d", 7) +} + +func newErrorWithCodefAtCallSite() error { + return stacktrace.NewErrorWithCodef(EcodeInvalidVillain, "coded %d", 7) +} + +func propagateWithCodefAtCallSite(err error) error { + return stacktrace.PropagateWithCodef(err, EcodeNotFastEnough, "coded propagate %d", 7) +} diff --git a/stacktrace.go b/stacktrace.go index 1032f8e..31c063a 100644 --- a/stacktrace.go +++ b/stacktrace.go @@ -50,6 +50,13 @@ func NewError(format string, args ...any) error { return create(nil, NoCode, format, args...) } +/* +NewErrorf is the formatting variant of NewError. +*/ +func NewErrorf(format string, args ...any) error { + return create(nil, NoCode, format, args...) +} + /* Propagate wraps an error to include line number information. The msg and vals arguments work like the ones for fmt.Errorf. @@ -89,6 +96,17 @@ func Propagate(err error, format string, args ...any) error { return create(err, NoCode, format, args...) } +/* +Propagatef is the formatting variant of Propagate. +*/ +func Propagatef(err error, format string, args ...any) error { + if err == nil { + // Allow calling Propagatef without checking whether there is error + return nil + } + return create(err, NoCode, format, args...) +} + /* ErrorCode is a code that can be attached to an error as it is passed/propagated up the stack. @@ -121,6 +139,13 @@ func NewErrorWithCode(code ErrorCode, format string, args ...any) error { return create(nil, code, format, args...) } +/* +NewErrorWithCodef is the formatting variant of NewErrorWithCode. +*/ +func NewErrorWithCodef(code ErrorCode, format string, args ...any) error { + return create(nil, code, format, args...) +} + /* PropagateWithCode is similar to Propagate but also attaches an error code. @@ -137,6 +162,17 @@ func PropagateWithCode(err error, code ErrorCode, format string, args ...any) er return create(err, code, format, args...) } +/* +PropagateWithCodef is the formatting variant of PropagateWithCode. +*/ +func PropagateWithCodef(err error, code ErrorCode, format string, args ...any) error { + if err == nil { + // Allow calling PropagateWithCodef without checking whether there is error + return nil + } + return create(err, code, format, args...) +} + /* NewMessageWithCode returns an error that prints just like fmt.Errorf with no line number, but including a code. The error code mechanism can be useful by @@ -154,6 +190,16 @@ func NewMessageWithCode(code ErrorCode, format string, args ...any) error { } } +/* +NewMessageWithCodef is the formatting variant of NewMessageWithCode. +*/ +func NewMessageWithCodef(code ErrorCode, format string, args ...any) error { + return &stacktrace{ + message: fmt.Sprintf(format, args...), + code: code, + } +} + /* GetCode extracts the error code from an error. diff --git a/stacktrace_test.go b/stacktrace_test.go index 6c91056..c52e42d 100644 --- a/stacktrace_test.go +++ b/stacktrace_test.go @@ -153,3 +153,89 @@ func TestExitCode(t *testing.T) { }) } } + +func TestFormattingHelpers(t *testing.T) { + root := errors.New("root") + + tests := []struct { + name string + err error + expected string + code stacktrace.ErrorCode + }{ + { + name: "new error", + err: stacktrace.NewErrorf("new %d", 7), + expected: "new 7", + code: stacktrace.NoCode, + }, + { + name: "propagate", + err: stacktrace.Propagatef(root, "propagate %d", 7), + expected: "propagate 7: root", + code: stacktrace.NoCode, + }, + { + name: "new error with code", + err: stacktrace.NewErrorWithCodef(EcodeInvalidVillain, "coded %d", 7), + expected: "coded 7", + code: EcodeInvalidVillain, + }, + { + name: "propagate with code", + err: stacktrace.PropagateWithCodef(root, EcodeNotFastEnough, "coded propagate %d", 7), + expected: "coded propagate 7: root", + code: EcodeNotFastEnough, + }, + { + name: "new message with code", + err: stacktrace.NewMessageWithCodef(EcodeInvalidVillain, "message %d", 7), + expected: "message 7", + code: EcodeInvalidVillain, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.expected, fmt.Sprintf("%#s", test.err)) + assert.Equal(t, test.code, stacktrace.GetCode(test.err)) + }) + } +} + +func TestFormattingHelpersPreserveCode(t *testing.T) { + err := stacktrace.NewErrorWithCodef(EcodeInvalidVillain, "inner %d", 7) + err = stacktrace.Propagatef(err, "outer %d", 8) + + assert.Equal(t, EcodeInvalidVillain, stacktrace.GetCode(err)) + assert.Equal(t, "outer 8: inner 7", fmt.Sprintf("%#s", err)) +} + +func TestFormattingPropagationNil(t *testing.T) { + assert.Nil(t, stacktrace.Propagatef(nil, "propagate %d", 7)) + assert.Nil(t, stacktrace.PropagateWithCodef(nil, EcodeInvalidVillain, "propagate %d", 7)) +} + +func TestFormattingHelpersCaptureCaller(t *testing.T) { + useFixturePaths(t) + root := errors.New("root") + + tests := []struct { + name string + err error + function string + }{ + {name: "new error", err: newErrorfAtCallSite(), function: "newErrorfAtCallSite"}, + {name: "propagate", err: propagatefAtCallSite(root), function: "propagatefAtCallSite"}, + {name: "new error with code", err: newErrorWithCodefAtCallSite(), function: "newErrorWithCodefAtCallSite"}, + {name: "propagate with code", err: propagateWithCodefAtCallSite(root), function: "propagateWithCodefAtCallSite"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + trace := fmt.Sprintf("%+s", test.err) + assert.Contains(t, trace, fixturePath("functions_for_test.go")) + assert.Contains(t, trace, "("+test.function+")") + }) + } +} From f7cc95c9097cbc8c6d1f1ce1ce9fe7cf2041cd68 Mon Sep 17 00:00:00 2001 From: Acho Arnold Ewin Date: Sun, 19 Jul 2026 15:38:05 +0300 Subject: [PATCH 4/7] docs: explain formatting helper APIs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1601a53e-b461-4419-af08-50a1839599b5 --- README.md | 52 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index c213187..630ede3 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ func WriteAll(baseDir string, entities []Entity) error { path := filepath.Join(baseDir, fileNameForEntity(ent)) err = Write(path, ent) if err != nil { - return stacktrace.Propagate(err, "Failed to write %v to %s", ent, path) + return stacktrace.Propagatef(err, "Failed to write %v to %s", ent, path) } } return nil @@ -86,20 +86,21 @@ func WriteAll(baseDir string, entities []Entity) error { ## Functions -#### `stacktrace.Propagate(err error, format string, args ...any) error` +#### `stacktrace.Propagatef(err error, format string, args ...any) error` -Propagate wraps an error to include line number information. This is going to be -your most common stacktrace call. +Propagatef wraps an error with a formatted message and line number +information. Use `Propagate` when the message has no formatting arguments. +Both functions return `nil` when `err` is nil. -As in all of these functions, the `format` and `args` work like `fmt.Errorf`. +The `format` and `args` work like `fmt.Sprintf`. -The message passed to Propagate should describe the action that failed, +The message passed to Propagatef should describe the action that failed, resulting in `err`. The canonical call looks like this: ```go result, err := process(arg) if err != nil { - return nil, stacktrace.Propagate(err, "Failed to process %v", arg) + return nil, stacktrace.Propagatef(err, "Failed to process %v", arg) } ``` @@ -132,14 +133,19 @@ little guilty every time you do this. This example also illustrates the behavior of Propagate when `err` is nil – it returns nil as well. There is no need to check `if err != nil`. -#### `stacktrace.NewError(format string, args ...any) error` +#### `stacktrace.NewErrorf(format string, args ...any) error` -NewError is a drop-in replacement for `fmt.Errorf` that includes line number -information. The canonical call looks like this: +NewErrorf is a drop-in replacement for `fmt.Errorf` that includes line number +information. Use `NewError` when the message has no formatting arguments. + +The `*f` helpers format their messages like `fmt.Sprintf`. Their counterparts +without `f` remain supported for compatibility. + +The canonical call looks like this: ```go if !IsOkay(arg) { - return stacktrace.NewError("Expected %v to be okay", arg) + return stacktrace.NewErrorf("Expected %v to be okay", arg) } ``` @@ -165,30 +171,32 @@ using that. NoCode is the error code of errors with no code explicitly attached. An ordinary `stacktrace.Propagate` preserves the error code of an error. -#### `stacktrace.PropagateWithCode(err error, code ErrorCode, format string, args ...any) error` +#### `stacktrace.PropagateWithCodef(err error, code ErrorCode, format string, args ...any) error` -#### `stacktrace.NewErrorWithCode(code ErrorCode, format string, args ...any) error` +#### `stacktrace.NewErrorWithCodef(code ErrorCode, format string, args ...any) error` -PropagateWithCode and NewErrorWithCode are analogous to Propagate and NewError -but also attach an error code. +#### `stacktrace.NewMessageWithCodef(code ErrorCode, format string, args ...any) error` + +PropagateWithCodef, NewErrorWithCodef, and NewMessageWithCodef are analogous to +Propagatef, NewErrorf, and NewMessageWithCode respectively, but also attach an +error code. Their non-`f` counterparts remain supported for compatibility. +PropagateWithCodef retains nil propagation. ```go _, err := os.Stat(manifestPath) if os.IsNotExist(err) { - return stacktrace.PropagateWithCode(err, EcodeManifestNotFound, "") + return stacktrace.PropagateWithCodef(err, EcodeManifestNotFound, "") } ``` -#### `stacktrace.NewMessageWithCode(code ErrorCode, format string, args ...any) error` - The error code mechanism can be useful by itself even where stack traces with -line numbers are not required. NewMessageWithCode returns an error that prints -just like `fmt.Errorf` with no line number, but including a code. +line numbers are not required. `NewMessageWithCodef` returns an error that +prints just like `fmt.Sprintf`, but including a code. ```go ttl := req.URL.Query().Get("ttl") if ttl == "" { - return 0, stacktrace.NewMessageWithCode(EcodeBadInput, "Missing ttl query parameter") + return 0, stacktrace.NewMessageWithCodef(EcodeBadInput, "Missing ttl query parameter") } ``` @@ -204,7 +212,7 @@ for i := 0; i < attempts; i++ { } // try a few more times } -return stacktrace.NewError("timed out after %d attempts", attempts) +return stacktrace.NewErrorf("timed out after %d attempts", attempts) ``` GetCode returns the special value `stacktrace.NoCode` if `err` is nil or if From 699256b1177e3fd9c3b955661d93364318680bb6 Mon Sep 17 00:00:00 2001 From: Acho Arnold Ewin Date: Sun, 19 Jul 2026 15:42:29 +0300 Subject: [PATCH 5/7] docs: fix formatting helper examples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 630ede3..45827c8 100644 --- a/README.md +++ b/README.md @@ -179,24 +179,26 @@ An ordinary `stacktrace.Propagate` preserves the error code of an error. PropagateWithCodef, NewErrorWithCodef, and NewMessageWithCodef are analogous to Propagatef, NewErrorf, and NewMessageWithCode respectively, but also attach an -error code. Their non-`f` counterparts remain supported for compatibility. -PropagateWithCodef retains nil propagation. +error code. Propagate and Propagatef inherit existing codes, while +PropagateWithCode and PropagateWithCodef override them with the supplied code. +Their non-`f` counterparts remain supported for compatibility. PropagateWithCodef +retains nil propagation. ```go _, err := os.Stat(manifestPath) if os.IsNotExist(err) { - return stacktrace.PropagateWithCodef(err, EcodeManifestNotFound, "") + return stacktrace.PropagateWithCode(err, EcodeManifestNotFound, "") } ``` The error code mechanism can be useful by itself even where stack traces with line numbers are not required. `NewMessageWithCodef` returns an error that -prints just like `fmt.Sprintf`, but including a code. +formats its message like `fmt.Sprintf`, but including a code. ```go ttl := req.URL.Query().Get("ttl") if ttl == "" { - return 0, stacktrace.NewMessageWithCodef(EcodeBadInput, "Missing ttl query parameter") + return 0, stacktrace.NewMessageWithCode(EcodeBadInput, "Missing ttl query parameter") } ``` From 9aae29baf7006024a499e74307fc2bea8a91b8f3 Mon Sep 17 00:00:00 2001 From: Acho Arnold Ewin Date: Sun, 19 Jul 2026 15:59:38 +0300 Subject: [PATCH 6/7] docs: clarify formatting semantics Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1601a53e-b461-4419-af08-50a1839599b5 --- README.md | 10 +++++++--- stacktrace.go | 20 ++++++++++---------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 45827c8..fc00dd6 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Stacktrace adds compact, contextual call-site information to Go errors. -> [!NOTE] +> [!IMPORTANT] > This repository is an actively maintained fork of > [palantir/stacktrace](https://github.com/palantir/stacktrace), which has been > inactive for many years. The fork preserves the original Apache-2.0-licensed @@ -135,12 +135,16 @@ This example also illustrates the behavior of Propagate when `err` is nil #### `stacktrace.NewErrorf(format string, args ...any) error` -NewErrorf is a drop-in replacement for `fmt.Errorf` that includes line number -information. Use `NewError` when the message has no formatting arguments. +NewErrorf creates an error with a formatted message and line number information. +Use `NewError` when the message has no formatting arguments. The `*f` helpers format their messages like `fmt.Sprintf`. Their counterparts without `f` remain supported for compatibility. +> [!NOTE] +> Unlike `fmt.Errorf`, `%w` is not supported; use `Propagate` or `Propagatef` to +> wrap a cause. + The canonical call looks like this: ```go diff --git a/stacktrace.go b/stacktrace.go index 31c063a..7ab5cf8 100644 --- a/stacktrace.go +++ b/stacktrace.go @@ -39,8 +39,8 @@ stacktraces, use something like: var CleanPath = cleanpath.RemoveGoPath /* -NewError is a drop-in replacement for fmt.Errorf that includes line number -information. The canonical call looks like this: +NewError creates an error with a formatted message and line number information. +The canonical call looks like this: if !IsOkay(arg) { return stacktrace.NewError("Expected %v to be okay", arg) @@ -59,7 +59,7 @@ func NewErrorf(format string, args ...any) error { /* Propagate wraps an error to include line number information. The msg and vals -arguments work like the ones for fmt.Errorf. +arguments work like the ones for fmt.Sprintf. The message passed to Propagate should describe the action that failed, resulting in the cause. The canonical call looks like this: @@ -90,7 +90,7 @@ nil" checks. */ func Propagate(err error, format string, args ...any) error { if err == nil { - // Allow calling Propagate without checking whether there is error + // Allow calling Propagate without checking whether there is an error return nil } return create(err, NoCode, format, args...) @@ -101,7 +101,7 @@ Propagatef is the formatting variant of Propagate. */ func Propagatef(err error, format string, args ...any) error { if err == nil { - // Allow calling Propagatef without checking whether there is error + // Allow calling Propagatef without checking whether there is an error return nil } return create(err, NoCode, format, args...) @@ -156,7 +156,7 @@ PropagateWithCode is similar to Propagate but also attaches an error code. */ func PropagateWithCode(err error, code ErrorCode, format string, args ...any) error { if err == nil { - // Allow calling PropagateWithCode without checking whether there is error + // Allow calling PropagateWithCode without checking whether there is an error return nil } return create(err, code, format, args...) @@ -167,16 +167,16 @@ PropagateWithCodef is the formatting variant of PropagateWithCode. */ func PropagateWithCodef(err error, code ErrorCode, format string, args ...any) error { if err == nil { - // Allow calling PropagateWithCodef without checking whether there is error + // Allow calling PropagateWithCodef without checking whether there is an error return nil } return create(err, code, format, args...) } /* -NewMessageWithCode returns an error that prints just like fmt.Errorf with no -line number, but including a code. The error code mechanism can be useful by -itself even where stack traces with line numbers are not warranted. +NewMessageWithCode returns an error whose message is formatted like fmt.Sprintf +with no line number, but including a code. The error code mechanism can be +useful by itself even where stack traces with line numbers are not warranted. ttl := req.URL.Query().Get("ttl") if ttl == "" { From c8eab77c524bd075e9ee4cfd14cd1470733ecfd4 Mon Sep 17 00:00:00 2001 From: Acho Arnold Ewin Date: Sun, 19 Jul 2026 16:04:29 +0300 Subject: [PATCH 7/7] docs: align Propagate parameter names Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1601a53e-b461-4419-af08-50a1839599b5 --- stacktrace.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stacktrace.go b/stacktrace.go index 7ab5cf8..397b097 100644 --- a/stacktrace.go +++ b/stacktrace.go @@ -58,7 +58,7 @@ func NewErrorf(format string, args ...any) error { } /* -Propagate wraps an error to include line number information. The msg and vals +Propagate wraps an error to include line number information. The format and args arguments work like the ones for fmt.Sprintf. The message passed to Propagate should describe the action that failed,