# fix(trail): fall back to list body in 'trail show' when detail fetch fails

`72da75f`→[main](/content/gh/entireio/cli/commits/main/index.html)·

Soph·3w ago·2 files·+21 added/-11 removed

The description block in 'trail show' dropped the body entirely whenever the best-effort detail fetch failed or the trail had no number (the enrich block is number-gated), even though the list body was already in hand. Seed the description from found.Body so degraded paths still render something; a successful detail fetch supersedes it with the richer body_document text.

Also note the prod web-origin assumption on trailWebURL and rename TestFetchTrailDetail_* to match the fetchTrailDescription it exercises.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

## Sessions

54665887b1d8View transcript

## Changes

2

- cmd/entire/cli

- Mtrail_cmd.go+20/-10

- Mtrail_cmd_test.go+1/-1

```
130 unmodified lines

131
132
133
134
134
135
136
137
138
139
140
137
138
139
140
141
142
143
144
144
145
146
147
148
149
150
151
152
153
154
147
148
155
156
157
158
159
82 unmodified lines

242
243
244
237
238
245
246
247
248
249
250
251

130 unmodified lines

// rendered description (trail.body_document.text_snapshot) the list
		// omits, and surface a browser URL. The detail fetch is best-effort:
		// the core metadata already came from the list, so a detail failure
		// degrades to "no description" with a warning rather than failing.
		// falls back to the list body with a warning rather than failing.
		m := found.ToMetadata()
		webURL := ""
		// The description lives only in the detail response (the list omits the
		// body), so it's only known when the detail fetch below succeeds.
		bodyText := ""
		descriptionLoaded := false
		// Seed the description from the list body so a failed (or skipped)
		// detail fetch still shows something; a successful detail fetch
		// supersedes it with the richer body_document text below.
		bodyText := found.Body
		descriptionLoaded := strings.TrimSpace(found.Body) != ""
		if found.Number > 0 {
			webURL = trailWebURL(api.BaseURL(), forge, owner, repo, found.Number)
			if bt, derr := fetchTrailDescription(ctx, client, forge, owner, repo, found.Number); derr == nil {
				bodyText = bt
				// A successful fetch means we authoritatively consulted the
				// description, but it only supersedes the seeded list body when
				// it actually carries text: an older/partial server that omits
				// body_document returns "" here and must not blank out a list
				// body that is present.
				descriptionLoaded = true
				if strings.TrimSpace(bt) != "" {
					bodyText = bt
				}
			} else {
				// Best-effort: warn but still render metadata + URL rather than
				// failing the whole command.
				// Best-effort: warn but still render metadata + URL (and the
				// list body) rather than failing the whole command.
				fmt.Fprintf(errW, "Warning: could not load trail description: %v\n", derr)
			}
		}
82 unmodified lines

}

// trailWebURL builds the browser URL for a trail:
// <web-origin>/<forge>/<owner>/<repo>/trails/<number>. The web app is co-hosted
// with the data API, so the API base URL is the web origin.
// <web-origin>/<forge>/<owner>/<repo>/trails/<number>. In production the web app
// is served from the same origin as the data API, so the API base URL doubles
// as the web origin. A split local-dev setup (API and frontend on different
// ports) would point this at the API port rather than the dev frontend.
func trailWebURL(base, forge, owner, repo string, number int) string {
	return strings.TrimRight(base, "/") + "/" + forge + "/" + owner + "/" + repo + "/trails/" + strconv.Itoa(number)
}
```

Mcmd/entire/cli/trail_cmd.go+20/-10

```
322 unmodified lines

323
324
325
326
326
327
328
329

322 unmodified lines

}
}

func TestFetchTrailDetail_ReadsNestedBodyDocument(t *testing.T) {
func TestFetchTrailDescription_ReadsNestedBodyDocument(t *testing.T) {
	t.Parallel()
	var gotPath string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
```
