fix(cli): keep enable's internal warnings out of the terminal; humanize decode failures · Entire
fix(cli): keep enable's internal warnings out of the terminal; humanize decode failures
73d4c63·
peyton-alt·yesterday·3 files·+50 added/-0 removed
enable never called logging.Init, so ladder warnings fell back to slog's stderr default and leaked ogen's full decoder chain into the flow's output (seen live when a gateway 502 failed the mirror offer). Init logging to .entire/logs after the bootstrap block, so a declined bootstrap leaves no stray .entire/ behind.
renderCoreError now collapses undecodable responses (gateway error pages — no typed error exists on that path) to one line: 'the control plane returned HTTP 502 — try again shortly' instead of the raw wrap chain. Applies to every control-plane command.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Sessions
01KXPKG9PAXZHXMP0GX6SRWEBFView transcript
[?
Fix Onboarding History and Scan Cache IssuesClaude Code·Fable 5·14 steps](/content/gh/entireio/cli/session/4c8ea8be-3447-426e-b8cb-07f2c8a04b71#timeline-01KXPKG9PAXZHXMP0GX6SRWEBF/index.html)
Changes
3
cmd/entire/cli
Mcorecmd.go+16
Mcorecmd_test.go+24
Msetup.go+10
5 unmodified lines
6
7
8
9
10
11
12
475 unmodified lines
488
489
490
491
492
493
494
495
496
497
498
499
1 unmodified line
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
5 unmodified lines
"errors"
"fmt"
"io"
"regexp"
"strings"
"charm.land/huh/v2";
475 unmodified lines
// SilenceUsage, so the message reaches the user without a usage dump. (A
// SilentError here would be swallowed — main.go skips printing those —
// leaving e.g. a 409 conflict with no output.)
// coreErrorStatusRe extracts the HTTP status from ogen's generated decoder
// wrapping ("default (code 502)"), the only place the status survives when a
// response fails to decode — e.g. a gateway 502 serving a text/plain error
// page — since no typed error exists on that path.
var coreErrorStatusRe = regexp.MustCompile(`\(code (\d{3})\)`)
func renderCoreError(err error) error {
if err == nil {
return nil
}
if msg := coreapi.APIError(err); msg != "" {
return errors.New(msg)
}
// A response that failed decoding renders as ogen's raw wrap chain
// ("decode response: … unexpected Content-Type: text/plain") — developer
// noise where a user needs one line. Collapse it to the status.
if m := coreErrorStatusRe.FindStringSubmatch(err.Error()); m != nil {
if m[1][0] == '5' {
return fmt.Errorf("the control plane returned HTTP %s — try again shortly", m[1])
}
return fmt.Errorf("control-plane request failed with HTTP %s", m[1])
}
return err
}
Mcmd/entire/cli/corecmd.go+16
146 unmodified lines
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
146 unmodified lines
t.Errorf("printFields output:\n%q\nwant:\n%q", got, want)
}
}
// A response that fails decoding (e.g. a gateway 502 serving text/plain)
// carries its status only in ogen's wrap string; renderCoreError must
// collapse that developer noise to a one-line message instead of showing the
// raw decoder chain (it reached a user's terminal verbatim in the enable
// mirror offer).
func TestRenderCoreError_CollapsesDecodeFailures(t *testing.T) {
t.Parallel()
raw := errors.New(`decode response: default (code 502): unexpected Content-Type: text/plain`)
got := renderCoreError(raw)
if got.Error() != "the control plane returned HTTP 502 — try again shortly" {
t.Errorf("renderCoreError(502 decode) = %q", got.Error())
}
client := errors.New(`decode response: default (code 404): unexpected Content-Type: text/html`)
if got := renderCoreError(client); got.Error() != "control-plane request failed with HTTP 404" {
t.Errorf("renderCoreError(404 decode) = %q", got.Error())
}
plain := errors.New("dial tcp: connection refused")
if got := renderCoreError(plain); !errors.Is(got, plain) {
t.Errorf("renderCoreError(plain) = %v, want passthrough", got)
}
}
Mcmd/entire/cli/corecmd_test.go+24
897 unmodified lines
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
897 unmodified lines
}
// Operational logging (ladder probes, offer failures) belongs in
// .entire/logs — without Init the logging package falls back to
// slog's stderr default and WARN lines leak verbatim into
// enable's output. After the bootstrap block, so a declined
// bootstrap doesn't leave a stray .entire/ in a non-repo folder.
logging.SetLogLevelGetter(GetLogLevel)
if lerr := logging.Init(ctx, ""); lerr == nil {
defer logging.Close()
}
if err := validateSetupFlags(opts.UseLocalSettings, opts.UseProjectSettings); err != nil {
return err
}