fix: address review + green CI for trail context injection · Entire
fix: address review + green CI for trail context injection
1144985→main·
dipree·1mo ago·6 files·+39 added/-93 removed
- Track once-per-session injection on session.State (ContextInjectionDecided) instead of a separate .injected marker file. The marker lived in .git/entire-sessions/ and broke integration tests that count dir entries and read stateFiles[0] (it sorted before .json). Removes ClaimContextInjection. - Bound NewAuthenticatedAPIClient under the same probe timeout as the HTTP call (it can do /.well-known discovery + token exchange) — Copilot review. - Drain the TrailsEnabled response body before close for connection reuse — Copilot review. - gofmt inject_test.go (lint).
Sessions
2df0d7859de4View transcript
[?
Inject Trail Context into Agent ModelPi·Opus 4.8·1 step](/content/gh/entireio/cli/session/019ecbec-8bcf-7f35-a647-2fc26c08a22e#timeline-2df0d7859de4/index.html)
Changes
6
cmd/entire/cli
agent
Minject_test.go+1
api
Mtrails.go+3
Mlifecycle.go+28/-17
session
Mstate.go+7
strategy
Msession_state.go-29
Msession_state_test.go-47
51 unmodified lines
type injectorStub struct{ Agent }
func (injectorStub) InjectionEvent() EventType { return TurnStart }
//nolint:unparam // signature is dictated by the ContextInjector interface
func (injectorStub) RenderContextInjection(ContextInjection) ([]byte, error) {
return []byte("x"), nil
}
Mcmd/entire/cli/agent/inject_test.go+1
2 unmodified lines
import (
"context"
"fmt"
"io"
"net/http"
)
11 unmodified lines
return false, fmt.Errorf("probe trails enablement: %w", err)
}
defer resp.Body.Close()
// Drain (bounded) so net/http can reuse the connection; the body is unused.
_, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 1<<16)) //nolint:errcheck // best-effort drain
return resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices, nil
}
Mcmd/entire/cli/api/trails.go+3
// trailsEnabledForRepo reports whether Entire trails are literally enabled for
// this repo on the API. It resolves the origin remote to a supported forge and
// probes the trails endpoint; a successful response means trails are
// provisioned/enabled. Best-effort and bounded by a short timeout — an
// unresolved remote, missing auth, or any API/transport error reports false, so
// we never advertise trails we can't confirm are enabled.
func trailsEnabledForRepo(ctx context.Context) bool {
forge, owner, repo, err := resolveTrailRemote(ctx)
if err != nil {
return false
}
client, err := NewAuthenticatedAPIClient(ctx, false)
if err != nil {
return false // not authenticated → trails aren't enabled for us
}
probeCtx, cancel := context.WithTimeout(ctx, trailsEnabledProbeTimeout)
defer cancel()
client, err := NewAuthenticatedAPIClient(probeCtx, false)
if err != nil {
return false // not authenticated → trails aren't enabled for us
}
enabled, err := client.TrailsEnabled(probeCtx, forge, owner, repo)
return err == nil && enabled
}
Mcmd/entire/cli/lifecycle.go+28/-17
224 unmodified lines
// ClaimContextInjection records that Entire's model-facing context injection
// has been emitted for a session, so the injection happens at most once per
// session. First-writer-wins, mirroring ClaimSessionStartBanner (and cleaned up
// by ClearSessionState, which removes every "<sessionID>" file).
//
// Returns (claimed=true) when this call won the race and the caller should emit
// the injection; (claimed=false) when an earlier call already claimed it.
func ClaimContextInjection(ctx context.Context, sessionID string) (claimed bool, err error) {
if vErr := validation.ValidateSessionID(sessionID); vErr != nil {
return false, fmt.Errorf("invalid session ID: %w", vErr)
}
root, rErr := openSessionStateRoot(ctx)
if rErr != nil {
return false, rErr
}
defer root.Close()
f, oErr := root.OpenFile(sessionID+".injected", os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600)
if oErr != nil {
if errors.Is(oErr, os.ErrExist) {
return false, nil
}
return false, fmt.Errorf("failed to create injection marker file: %w", oErr)
}
_ = f.Close()
return true, nil
}
Mcmd/entire/cli/session/state.go+7
372 unmodified lines
// LoadAgentTypeHint reads the agent type hint written by SessionStart.
// Returns empty string if the hint file doesn't exist, can't be read, or the
// value isn't a registered agent type.
Mcmd/entire/cli/strategy/session_state.go-29
997 unmodified lines
}
}
func TestClaimContextInjection_FirstWriterWins(t *testing.T) {
dir := t.TempDir()
_, err := git.PlainInit(dir, false)
require.NoError(t, err)
t.Chdir(dir)
ctx := context.Background()
sessionID := "2026-01-01-injection-claim"
claimed, err := ClaimContextInjection(ctx, sessionID)
require.NoError(t, err)
require.True(t, claimed, "first call must win the injection claim")
claimed, err = ClaimContextInjection(ctx, sessionID)
require.NoError(t, err)
require.False(t, claimed, "subsequent calls must report the injection already claimed")
}
func TestClaimContextInjection_InvalidSessionID_ReturnsError(t *testing.T) {
dir := t.TempDir()
_, err := git.PlainInit(dir, false)
require.NoError(t, err)
t.Chdir(dir)
_, err = ClaimContextInjection(context.Background(), "../../../etc/passwd")
require.Error(t, err)
}
func TestClearSessionState_RemovesInjectionMarker(t *testing.T) {
dir := t.TempDir()
_, err := git.PlainInit(dir, false)
require.NoError(t, err)
t.Chdir(dir)
ctx := context.Background()
sessionID := "2026-01-01-clear-injection"
_, err = ClaimContextInjection(ctx, sessionID)
require.NoError(t, err)
require.NoError(t, ClearSessionState(ctx, sessionID))
// After clear, the marker is gone — the next claim wins again.
claimed, err := ClaimContextInjection(ctx, sessionID)
require.NoError(t, err)
require.True(t, claimed, "ClearSessionState should remove the injection marker")
}
Mcmd/entire/cli/strategy/session_state_test.go-47