From 4364e1d8a454e438b5436b7ca2091c5fd5d79f30 Mon Sep 17 00:00:00 2001 From: cyph3rasi Date: Sat, 11 Jul 2026 19:42:05 -0700 Subject: [PATCH] Make prompt capture fast and resilient Hop-State: A_06FN8D0CK06GY5XBDYZ632R Hop-Proposal: R_06FN8CY2FJBV5QA0WFEDWHR Hop-Task: T_06FN8C4PMFJRT8APV4G03Y8 Hop-Attempt: AT_06FN8C4PMFRJEE4B7KKQ740 --- .../prompts/P_06FN8C4PMD47GH10ZFQY2VR.json | 17 ++++++++++++++ internal/hop/git.go | 21 ++++++++++++++++++ internal/hop/service.go | 22 ++++++++----------- skills/hop/SKILL.md | 9 ++++++-- 4 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 .hop/records/prompts/P_06FN8C4PMD47GH10ZFQY2VR.json diff --git a/.hop/records/prompts/P_06FN8C4PMD47GH10ZFQY2VR.json b/.hop/records/prompts/P_06FN8C4PMD47GH10ZFQY2VR.json new file mode 100644 index 0000000..e35e875 --- /dev/null +++ b/.hop/records/prompts/P_06FN8C4PMD47GH10ZFQY2VR.json @@ -0,0 +1,17 @@ +{ + "id": "P_06FN8C4PMD47GH10ZFQY2VR", + "task_id": "T_06FN8C4PMFJRT8APV4G03Y8", + "attempt_id": "AT_06FN8C4PMFRJEE4B7KKQ740", + "state_id": "P_06FN8C4PMD47GH10ZFQY2VR", + "prompt": "\"I couldn’t safely start the repository change because Hop prompt capture timed out. Per the repository workflow, I stopped before inspecting or modifying files. No changes were made.\" what the fuck is this bullshit", + "agent_name": "codex", + "status": "proposed", + "response_summary": "Make prompt capture fast and resilient", + "created_at": "2026-07-12T02:38:18.275042Z", + "metadata": { + "source_tree": "7f3f889e96a317c157eedf4f5285af835865f6fb", + "git_commit": "ff62e89596a85ff3073ea348919b8d2b7125069f", + "attempt_head": "C_06FN8CQ0ZNZ60MRZ818MB9G", + "attempt_head_kind": "checkpoint" + } +} diff --git a/internal/hop/git.go b/internal/hop/git.go index a7ffeac..f488ca5 100644 --- a/internal/hop/git.go +++ b/internal/hop/git.go @@ -655,6 +655,27 @@ func (r *Repository) ReadHiddenRef(ctx context.Context, name string) (oid string return r.ReadRef(ctx, ref) } +// ListHiddenRefs reads all refs below refs/hop in one Git process. Keys are +// relative to refs/hop/ (for example, "states/P_..." or "accepted"). +func (r *Repository) ListHiddenRefs(ctx context.Context) (map[string]string, error) { + output, err := r.run(ctx, nil, nil, "for-each-ref", "--format=%(refname) %(objectname)", hiddenRefPrefix) + if err != nil { + return nil, err + } + refs := make(map[string]string) + for _, line := range strings.Split(strings.TrimSpace(output), "\n") { + if line == "" { + continue + } + name, oid, found := strings.Cut(line, " ") + if !found || !strings.HasPrefix(name, hiddenRefPrefix) { + return nil, fmt.Errorf("unexpected hidden ref listing %q", line) + } + refs[strings.TrimPrefix(name, hiddenRefPrefix)] = strings.TrimSpace(oid) + } + return refs, nil +} + // UpdateHiddenRef atomically updates refs/hop/name. With no expectedOld value, // it is unconditional. Passing expectedOld performs compare-and-swap; an empty // expected value means the ref must not exist. diff --git a/internal/hop/service.go b/internal/hop/service.go index dce3237..221e96a 100644 --- a/internal/hop/service.go +++ b/internal/hop/service.go @@ -1640,17 +1640,17 @@ func (s *Service) reconcileDerivedRefs(ctx context.Context, reconcileAccepted bo if err != nil { return err } + refs, err := s.Repo.ListHiddenRefs(ctx) + if err != nil { + return err + } for _, row := range rows { state := row.State - if err := s.Repo.VerifyObjects(ctx, state.GitCommit, state.SourceTree); err != nil { - return fmt.Errorf("state %s references unavailable Git data: %w", state.ID, err) - } name := "states/" + state.ID - commit, exists, err := s.Repo.ReadHiddenRef(ctx, name) - if err != nil { - return err - } - if !exists || commit != state.GitCommit { + if refs[name] != state.GitCommit { + if err := s.Repo.VerifyObjects(ctx, state.GitCommit, state.SourceTree); err != nil { + return fmt.Errorf("state %s references unavailable Git data: %w", state.ID, err) + } if err := s.Repo.UpdateHiddenRef(ctx, name, state.GitCommit); err != nil { return fmt.Errorf("repair Git pin for state %s: %w", state.ID, err) } @@ -1668,11 +1668,7 @@ func (s *Service) reconcileDerivedRefs(ctx context.Context, reconcileAccepted bo if err != nil { return err } - commit, exists, err := s.Repo.ReadHiddenRef(ctx, acceptedRef) - if err != nil { - return err - } - if !exists || commit != head.GitCommit { + if refs[acceptedRef] != head.GitCommit { if err := s.Repo.UpdateHiddenRef(ctx, acceptedRef, head.GitCommit); err != nil { return fmt.Errorf("repair accepted Git ref: %w", err) } diff --git a/skills/hop/SKILL.md b/skills/hop/SKILL.md index 58899b7..0c0c527 100644 --- a/skills/hop/SKILL.md +++ b/skills/hop/SKILL.md @@ -58,8 +58,13 @@ uses it as the default session and identifies the runtime as `codex` unless headers, and credential-bearing connection strings before persistence. Read the returned `HOP_STATE_ID`, `HOP_TASK_ID`, `HOP_ATTEMPT_ID`, and workspace. -If capture fails or `hop` is unavailable, stop without project effects and -report the error. +Allow at least 120 seconds for capture. If it times out or fails transiently, +retry once with the same message, agent, and session; Hop session binding makes +the retry idempotent for the task. If the retry fails, inspect Hop's error and +locks, run `hop doctor`, and repair a safe local operational problem when +possible. Stop without project effects only when Hop remains unavailable or +unsafe after recovery; do not abandon the user's request after the first +timeout. If Hop reports redactions, never repeat the credential in output, summaries, commands recorded as evidence, or proposal text. Refer to its environment