fix(hooks): guard detached trail-refresh logging on WorktreeRoot · Entire

fix(hooks): guard detached trail-refresh logging on WorktreeRoot

c072c5f→main·

suhaanthayyil·6d ago·2 files·+32 added/-4 removed

newRefreshTrailEnablementCmd initialized file logging unconditionally, unlike resume/rewind/reset/explain, which guard on paths.WorktreeRoot first to avoid creating .entire/logs/ in arbitrary directories. logging.Init falls back to the current directory when WorktreeRoot fails, so a detached child whose worktree was removed or relocated between spawn and exec — or a manual invocation outside a repo — would MkdirAll a stray .entire/logs/ wherever it happened to run. Wrap the logging setup in the same WorktreeRoot guard, mutation-verified by a test that runs the command in a non-worktree temp dir and asserts no .entire/logs appears.

Changes

2

2368 unmodified lines

2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396

2368 unmodified lines

"background refresh failure must be diagnosable in .entire/logs/entire.log (#450)")
}

// TestRefreshTrailEnablementCmd_NoStrayLogsOutsideWorktree guards the file-init
// against running outside a resolvable worktree. logging.Init falls back to the
// current directory when paths.WorktreeRoot fails, so the command must guard on
// WorktreeRoot (as resume/rewind/reset/explain do) or a child whose worktree was
// removed/relocated between spawn and exec would MkdirAll a stray .entire/logs/
// wherever it happens to be running.
func TestRefreshTrailEnablementCmd_NoStrayLogsOutsideWorktree(t *testing.T) {
    dir := t.TempDir() // a plain temp dir, not a git worktree
    t.Chdir(dir)
    paths.ClearWorktreeRootCache()
    session.ClearGitCommonDirCache()
    t.Setenv("ENTIRE_LOG_LEVEL", "debug")

cmd := newRefreshTrailEnablementCmd()
    cmd.SetArgs([]string{})
    require.NoError(t, cmd.ExecuteContext(context.Background()))

_, statErr := os.Stat(filepath.Join(dir, ".entire", "logs"))
    require.True(t, os.IsNotExist(statErr),
        "must not create a stray .entire/logs outside a resolvable worktree")
}

// TestTrailRefreshRecentlySpawned_ThrottlesWithinWindow verifies the spawn-side
// guard (#450 follow-up): within trailRefreshSpawnThrottle of a recorded spawn,
// further spawns are suppressed; once the window passes a fresh spawn is allowed

Mcmd/entire/cli/lifecycle_test.go+22

340 unmodified lines

341
342
343
344
345
346
347
344
345
346
347
348
349
350
351
352
353
354
355
356

340 unmodified lines

// Detached child with discarded stdout/stderr: initialize file
        // logging so a failing background refresh (the #450 unreachable-host
        // symptom) is diagnosable in .entire/logs/entire.log rather than
        // vanishing. Best-effort, mirroring the other hook-side commands.
        logging.SetLogLevelGetter(GetLogLevel)
        if err := logging.Init(ctx, ""); err == nil {
            defer logging.Close()
        // vanishing. Guard on WorktreeRoot first — matching resume/rewind/
        // reset/explain — so a child whose worktree was removed or relocated
        // between spawn and exec (or a manual invocation outside a repo)
        // doesn't create a stray .entire/logs/ in an arbitrary directory;
        // logging.Init falls back to cwd when WorktreeRoot fails.
        if _, err := paths.WorktreeRoot(ctx); err == nil {
            logging.SetLogLevelGetter(GetLogLevel)
            if err := logging.Init(ctx, ""); err == nil {
                defer logging.Close()
            }
        }
        return runTrailEnablementRefresh(ctx)
    },

Mcmd/entire/cli/trail_context_cache.go+10/-4