Skip to content

Commit 9a028ef

Browse files
author
aibuddy
committed
S02: clean-lint-fixes-tools — make stderr-JSON tests robust and green
Tighten tests to skip stdout decode on non-zero exit for tools’ standardized error JSON path; apply gofmt -s. All tools/* ErrorJSON tests now pass and full suite is green locally. Completes L225 S02 under the lint cleanup umbrella while preserving gates. Refs: #1
1 parent e563224 commit 9a028ef

7 files changed

Lines changed: 85 additions & 19 deletions

File tree

FEATURE_CHECKLIST.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,9 @@
222222
* [x] Emit precise HTTP timing and failure cause to the audit: record DNS/connect/TLS/write/read durations, status, and whether the context deadline fired vs server closed, and surface a concise user hint (“increase -http-timeout or reduce prompt/model latency”); DoD: new fields present in `.goagent/audit/*` with a passing test that asserts structure, performance unaffected, CI green, peer review done.
223223
* [x] Runbook entry for `context deadline exceeded`: add a troubleshooting section that explains causes (slow model, proxy timeouts, too-small `-http-timeout`), mitigation steps (raise `-http-timeout`, tune proxy `proxy_read_timeout`, reduce prompt size), and an example of enabling retries; DoD: docs render on GitHub, copy-paste checks complete, CI docs gates green, peer review done.
224224
* [x] Make relative paths in tools.json resolve against the directory containing that tools.json (not process CWD), preserving existing security checks that reject `..` escapes; update `internal/tools/manifest.go` to anchor, clean, and normalize paths cross-platform, adapt unit/integration tests to cover a manifest in a nested folder, adjust `make verify-manifest-paths` if needed, update `docs/reference/tools-manifest.md` to document the rule and absolute-path allowance for tests, and verify end-to-end that an agent run using a nested tools.json with relative `./tools/bin/*` works; DoD: unit/integration tests added and green on Linux/macOS/Windows, docs updated, CI and all quality gates green, one peer review completed.
225-
* [ ] Makefile: preserve the logs directory during the clean target by removing any deletion of logs while keeping other artifact removals; DoD: from a clean clone create a sentinel file under logs, build artifacts, run clean, verify the sentinel remains and artifacts are gone, repo status is clean, all gates (lint/vet/format/tests/security/secret) green, one peer review completed.
225+
* [x] Makefile: preserve the logs directory during the clean target by removing any deletion of logs while keeping other artifact removals; DoD: from a clean clone create a sentinel file under logs, build artifacts, run clean, verify the sentinel remains and artifacts are gone, repo status is clean, all gates (lint/vet/format/tests/security/secret) green, one peer review completed.
226226
- [x] [S01:clean-preserve-logs-verified] Verified `make clean` preserves `logs/` (sentinel survives) and removes artifacts (`tools/bin`, `bin`, `reports`, `.goagent`).
227-
- [ ] [S02:clean-lint-fixes-tools] Resolve repo-wide `golangci-lint` findings (errcheck/gocyclo in `tools/cmd/*`) so quality gates are green for this item.
227+
- [x] [S02:clean-lint-fixes-tools] Resolve repo-wide `golangci-lint` findings (errcheck/gocyclo in `tools/cmd/*`) so quality gates are green for this item.
228228
- [x] [S02d:errcheck-encode-and-visits] Check json.Encode errors in `fs_listdir`, `fs_read_lines`, `fs_stat`; handle `WalkDir`/`Info`/visit errors deterministically; update tests to check Unmarshal and cleanup removes.
229229
- [x] [S02a:lint-errcheck-fs_read_write] Fix errcheck in `tools/cmd/fs_read_file` and `tools/cmd/fs_write_file`; tests green.
230230
- [x] [S02b:lint-errcheck-batch-2] Address remaining errcheck in `get_time`, `fs_append_file`, and `fs_apply_patch` (encode/close and unsafe ignores); re-run lint.
@@ -248,3 +248,5 @@
248248
- [ ] [S02b:l207-install-rg] Blocked locally: ripgrep (rg) not installed; `make check-tools-paths` fails in lint. Next step: install ripgrep and rerun `make lint`.
249249
* [ ] (Contingency, removable later) Add a temporary CI lint job that runs with Go 1.23.x if the pinned linter still fails on 1.24.x; tests and build jobs stay on `go-version-file: go.mod`. Smallest change: duplicate the `lint` job as `lint-compat`, set `actions/setup-go@v5` `go-version: "1.23.x"`, run `make lint`, mark with a TODO comment referencing the issue to remove once ADR-0003 policy is fully green with 1.24.x. DoD: CI green with both lint jobs; issue description captures the intent to delete this path; peer review completed; rollback by deleting the `lint-compat` job.
250250
* [ ] Add a one-shot verifier in CI that prints versions for traceability. Smallest change: in the `lint` job, after setup and install, run `go version && $(GOBIN)/golangci-lint version` and emit them as step outputs (name the step “toolchain-versions”); no functional changes. DoD: CI logs show versions on all OSes; no gate regressions; peer review completed.
251+
* [ ] CLI flags order independence: make argument parsing accept flags in any order without requiring `-prompt` to appear first while preserving precedence (flag > env > default) and error semantics; add table-driven unit tests that permute common orders (e.g., `-prompt` before/after other flags, mixed with `-debug`, `-tools`, `--help`) asserting identical parsed values, `--help` exits 0 regardless of position, and “-prompt is required” uses exit code 2 only when truly absent; update README to state flags are order-insensitive; DoD: tests green on all OSes, CI and all quality gates green, one peer review completed.
252+
* [ ] Diagnose and fix opaque `context deadline exceeded` on chat POST by instrumenting HTTP phase timings (DNS/connect/TLS/write/read/idle), verifying and wiring `-http-timeout`/`OAI_HTTP_TIMEOUT` is actually applied to the OpenAI POST (independent from tool timeouts) or adding it if missing, and upgrading the surfaced error to include base URL, phase, and configured timeout with actionable hints (e.g., server unreachable vs slow response); add table-driven unit tests and an integration test with a fake slow server and a refused connection to prove (a) correct timeout behavior, (b) clear error text and exit code 1 for network/timeout, 2 for CLI misuse, and (c) that raising `-http-timeout` resolves the slow-server case; update README and the troubleshooting runbook accordingly; DoD: failing tests first then passing, running the provided repro command yields a precise cause message (not a generic context deadline), CI and all quality gates green, one peer review completed.

tools/cmd/fs_append_file/fs_append_file_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ func runFsAppend(t *testing.T, bin string, input any) (fsAppendOutput, string, i
4343
}
4444
}
4545
var out fsAppendOutput
46-
_ = json.Unmarshal([]byte(strings.TrimSpace(stdout.String())), &out)
46+
if code == 0 {
47+
if err := json.Unmarshal([]byte(strings.TrimSpace(stdout.String())), &out); err != nil {
48+
t.Fatalf("unmarshal stdout: %v; raw=%q", err, stdout.String())
49+
}
50+
}
4751
return out, stderr.String(), code
4852
}
4953

tools/cmd/fs_mkdirp/fs_mkdirp_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ func runFsMkdirp(t *testing.T, bin string, input any) (fsMkdirpOutput, string, i
4141
}
4242
}
4343
var out fsMkdirpOutput
44-
_ = json.Unmarshal(bytes.TrimSpace(stdout.Bytes()), &out)
44+
if code == 0 {
45+
if err := json.Unmarshal(bytes.TrimSpace(stdout.Bytes()), &out); err != nil {
46+
t.Fatalf("unmarshal stdout: %v; raw=%q", err, stdout.String())
47+
}
48+
}
4549
return out, stderr.String(), code
4650
}
4751

tools/cmd/fs_read_file/fs_read_file_test.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ func runFsRead(t *testing.T, bin string, input any) (fsReadOutput, string, int)
4646
}
4747
}
4848
var out fsReadOutput
49-
_ = json.Unmarshal([]byte(strings.TrimSpace(stdout.String())), &out)
49+
if code == 0 {
50+
if err := json.Unmarshal([]byte(strings.TrimSpace(stdout.String())), &out); err != nil {
51+
t.Fatalf("unmarshal stdout: %v; raw=%q", err, stdout.String())
52+
}
53+
}
5054
return out, stderr.String(), code
5155
}
5256

@@ -62,7 +66,11 @@ func makeRepoRelTempFile(t *testing.T, dirPrefix string, data []byte) (relPath s
6266
if err := os.WriteFile(fileRel, data, 0o644); err != nil {
6367
t.Fatalf("write temp file: %v", err)
6468
}
65-
t.Cleanup(func() { _ = os.RemoveAll(base) })
69+
t.Cleanup(func() {
70+
if err := os.RemoveAll(base); err != nil {
71+
t.Logf("cleanup remove %s: %v", base, err)
72+
}
73+
})
6674
return fileRel
6775
}
6876

@@ -117,7 +125,10 @@ func TestFsRead_Ranges(t *testing.T) {
117125
if code1 != 0 {
118126
t.Fatalf("expected success, got exit=%d stderr=%q", code1, stderr1)
119127
}
120-
b1, _ := base64.StdEncoding.DecodeString(out1.ContentBase64)
128+
b1, err := base64.StdEncoding.DecodeString(out1.ContentBase64)
129+
if err != nil {
130+
t.Fatalf("decode b1: %v", err)
131+
}
121132
if string(b1) != "cde" || out1.EOF {
122133
t.Fatalf("unexpected range1: content=%q eof=%v", string(b1), out1.EOF)
123134
}
@@ -126,7 +137,10 @@ func TestFsRead_Ranges(t *testing.T) {
126137
if code2 != 0 {
127138
t.Fatalf("expected success, got exit=%d stderr=%q", code2, stderr2)
128139
}
129-
b2, _ := base64.StdEncoding.DecodeString(out2.ContentBase64)
140+
b2, err := base64.StdEncoding.DecodeString(out2.ContentBase64)
141+
if err != nil {
142+
t.Fatalf("decode b2: %v", err)
143+
}
130144
if string(b2) != "fg" || !out2.EOF {
131145
t.Fatalf("unexpected range2: content=%q eof=%v", string(b2), out2.EOF)
132146
}

tools/cmd/fs_read_lines/fs_read_lines_test.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ func runFsReadLines(t *testing.T, bin string, input any) (fsReadLinesOutput, str
5151
}
5252
}
5353
var out fsReadLinesOutput
54-
_ = json.Unmarshal([]byte(strings.TrimSpace(stdout.String())), &out)
54+
if code == 0 {
55+
if err := json.Unmarshal([]byte(strings.TrimSpace(stdout.String())), &out); err != nil {
56+
t.Fatalf("unmarshal stdout: %v; raw=%q", err, stdout.String())
57+
}
58+
}
5559
return out, stderr.String(), code
5660
}
5761

@@ -64,7 +68,11 @@ func TestFsReadLines_LF_Simple(t *testing.T) {
6468
if err != nil {
6569
t.Fatalf("mkdir temp: %v", err)
6670
}
67-
t.Cleanup(func() { _ = os.RemoveAll(tmpDirAbs) })
71+
t.Cleanup(func() {
72+
if err := os.RemoveAll(tmpDirAbs); err != nil {
73+
t.Logf("cleanup remove %s: %v", tmpDirAbs, err)
74+
}
75+
})
6876
base := filepath.Base(tmpDirAbs)
6977
fileRel := filepath.Join(base, "data.txt")
7078
content := "l1\nl2\nl3\nl4\nl5\n"
@@ -103,7 +111,11 @@ func TestFsReadLines_CRLF_Normalize(t *testing.T) {
103111
if err != nil {
104112
t.Fatalf("mkdir temp: %v", err)
105113
}
106-
t.Cleanup(func() { _ = os.RemoveAll(tmpDirAbs) })
114+
t.Cleanup(func() {
115+
if err := os.RemoveAll(tmpDirAbs); err != nil {
116+
t.Logf("cleanup remove %s: %v", tmpDirAbs, err)
117+
}
118+
})
107119
base := filepath.Base(tmpDirAbs)
108120
fileRel := filepath.Join(base, "data.txt")
109121
// 5 lines with CRLF endings

tools/cmd/fs_search/fs_search_test.go

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,11 @@ func runFsSearch(t *testing.T, bin string, input any) (fsSearchOutput, string, i
5252
}
5353
}
5454
var out fsSearchOutput
55-
_ = json.Unmarshal([]byte(strings.TrimSpace(stdout.String())), &out)
55+
if code == 0 {
56+
if err := json.Unmarshal([]byte(strings.TrimSpace(stdout.String())), &out); err != nil {
57+
t.Fatalf("unmarshal stdout: %v; raw=%q", err, stdout.String())
58+
}
59+
}
5660
return out, stderr.String(), code
5761
}
5862

@@ -63,7 +67,11 @@ func TestFsSearch_Literal_SingleFile(t *testing.T) {
6367
if err != nil {
6468
t.Fatalf("mkdir temp: %v", err)
6569
}
66-
t.Cleanup(func() { _ = os.RemoveAll(tmpDirAbs) })
70+
t.Cleanup(func() {
71+
if err := os.RemoveAll(tmpDirAbs); err != nil {
72+
t.Logf("cleanup remove %s: %v", tmpDirAbs, err)
73+
}
74+
})
6775
base := filepath.Base(tmpDirAbs)
6876
fileRel := filepath.Join(base, "a.txt")
6977
content := "alpha\nbravo charlie\nalpha bravo\n"
@@ -116,7 +124,11 @@ func TestFsSearch_Regex_SingleFile(t *testing.T) {
116124
if err != nil {
117125
t.Fatalf("mkdir temp: %v", err)
118126
}
119-
t.Cleanup(func() { _ = os.RemoveAll(tmpDirAbs) })
127+
t.Cleanup(func() {
128+
if err := os.RemoveAll(tmpDirAbs); err != nil {
129+
t.Logf("cleanup remove %s: %v", tmpDirAbs, err)
130+
}
131+
})
120132
base := filepath.Base(tmpDirAbs)
121133
fileRel := filepath.Join(base, "r.txt")
122134
content := "alpha\nbravo charlie\nalpha bravo\n"
@@ -167,7 +179,11 @@ func TestFsSearch_Globs_Filter(t *testing.T) {
167179
if err != nil {
168180
t.Fatalf("mkdir temp: %v", err)
169181
}
170-
t.Cleanup(func() { _ = os.RemoveAll(tmpDirAbs) })
182+
t.Cleanup(func() {
183+
if err := os.RemoveAll(tmpDirAbs); err != nil {
184+
t.Logf("cleanup remove %s: %v", tmpDirAbs, err)
185+
}
186+
})
171187
base := filepath.Base(tmpDirAbs)
172188

173189
txtRel := filepath.Join(base, "note.txt")
@@ -222,7 +238,11 @@ func TestFsSearch_Truncation(t *testing.T) {
222238
if err != nil {
223239
t.Fatalf("mkdir temp: %v", err)
224240
}
225-
t.Cleanup(func() { _ = os.RemoveAll(tmpDirAbs) })
241+
t.Cleanup(func() {
242+
if err := os.RemoveAll(tmpDirAbs); err != nil {
243+
t.Logf("cleanup remove %s: %v", tmpDirAbs, err)
244+
}
245+
})
226246
base := filepath.Base(tmpDirAbs)
227247

228248
fileRel := filepath.Join(base, "many.txt")

tools/cmd/fs_write_file/fs_write_file_test.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ func runFsWrite(t *testing.T, bin string, input any) (fsWriteOutput, string, int
4242
}
4343
}
4444
var out fsWriteOutput
45-
_ = json.Unmarshal([]byte(strings.TrimSpace(stdout.String())), &out)
45+
if code == 0 {
46+
if err := json.Unmarshal([]byte(strings.TrimSpace(stdout.String())), &out); err != nil {
47+
t.Fatalf("unmarshal stdout: %v; raw=%q", err, stdout.String())
48+
}
49+
}
4650
return out, stderr.String(), code
4751
}
4852

@@ -91,7 +95,10 @@ func TestFsWrite_Overwrite(t *testing.T) {
9195
if out.BytesWritten != len(newContent) {
9296
t.Fatalf("bytesWritten mismatch: got %d want %d", out.BytesWritten, len(newContent))
9397
}
94-
readBack, _ := os.ReadFile(path)
98+
readBack, err := os.ReadFile(path)
99+
if err != nil {
100+
t.Fatalf("read back: %v", err)
101+
}
95102
if !bytes.Equal(readBack, newContent) {
96103
t.Fatalf("overwrite failed: got %q want %q", readBack, newContent)
97104
}
@@ -112,7 +119,10 @@ func TestFsWrite_Binary(t *testing.T) {
112119
if out.BytesWritten != len(data) {
113120
t.Fatalf("bytesWritten mismatch: got %d want %d", out.BytesWritten, len(data))
114121
}
115-
readBack, _ := os.ReadFile(path)
122+
readBack, err := os.ReadFile(path)
123+
if err != nil {
124+
t.Fatalf("read back: %v", err)
125+
}
116126
if !bytes.Equal(readBack, data) {
117127
t.Fatalf("binary mismatch: got %v want %v", readBack, data)
118128
}

0 commit comments

Comments
 (0)