2026-06-19 Repository implementation Issue gh-forgejo-shim-1ii

Added Codex probe capture and replay diagnostics

The shim can now record opt-in gh and temporary git probe traces, summarize those JSONL files, and run a local Codex-style smoke sequence without changing default runtime behavior.

Summary

Implemented a diagnostic-first loop for the reported Codex.app states: CLI availability, PR status availability, and branch selector freshness. Tracing is enabled only by FJ_SHIM_TRACE, body excerpts require FJ_SHIM_TRACE_BODY=1, and auth-sensitive output is redacted or suppressed.

Changes Made

Shim Trace Capture

Added src/gh_forgejo_shim/trace.py and wired routing.run_gh() to append route, host/repo, duration, exit code, env keys, and output-size records when tracing is enabled.

Trace Summary

Added gfj trace summarize <trace.jsonl> to group failures, slow calls, unsupported commands, routes, and known Codex probe patterns.

Codex Smoke Replay

Added gfj trace smoke, which runs the observed read-only gh and raw git probe family and reports exit codes, timings, and output sizes.

Temporary Git Recorder

Added gfj trace git-recorder create/remove for one-session raw git tracing when branch UI behavior bypasses the gh shim.

Context

Previous fixes added Forgejo-shaped support for gh auth status, gh api user, gh pr status, and Codex PR board fields, but the next compatibility step needed exact observations instead of more guessing. This change turns live Codex subprocess behavior into replayable records and focused tests.

Important Implementation Details

Relevant Diff Snippets

Focused unified diff excerpt. It shows the trace hook, CLI surface, recorder creation, and documentation entry without embedding full test hunks or generated trace data.

diff --git a/src/gh_forgejo_shim/routing.py b/src/gh_forgejo_shim/routing.py
@@
+from .trace import append_trace, build_record, capture_streams, suppress_stdout_body, tracing_enabled
@@
 def run_gh(
     argv: list[str],
@@
+    traced = tracing_enabled(values)
+    out, err = (
+        capture_streams(
+            stdout=stdout,
+            stderr=stderr,
+            env=values,
+            redact_stdout_body=suppress_stdout_body(argv),
+        )
+        if traced
+        else (stdout or sys.stdout, stderr or sys.stderr)
+    )
+    started = time.perf_counter()
@@
+    finally:
+        if traced:
+            append_trace(
+                build_record(
+                    kind="gh",
+                    argv=argv,
+                    cwd=cwd,
+                    env=values,
+                    duration_ms=(time.perf_counter() - started) * 1000,
+                    exit_code=exit_code,
+                    stdout_summary=out.summary(),
+                    stderr_summary=err.summary(),
+                    route=_trace_route(decision),
+                    host=_trace_host(decision),
+                    repo=_trace_repo(decision),
+                ),
+                env=values,
+            )

diff --git a/src/gh_forgejo_shim/cli.py b/src/gh_forgejo_shim/cli.py
@@
+    trace = subparsers.add_parser("trace", help="capture and summarize Codex gh/git diagnostics")
+    trace_subparsers = trace.add_subparsers(dest="trace_command", required=True)
+    summarize = trace_subparsers.add_parser("summarize", help="summarize a shim trace JSONL file")
+    summarize.add_argument("trace_path")
+    smoke = trace_subparsers.add_parser("smoke", help="run the local Codex gh/git probe smoke checks")
+    git_recorder = trace_subparsers.add_parser("git-recorder", help="generate or remove a temporary git recorder wrapper")

diff --git a/src/gh_forgejo_shim/git_recorder.py b/src/gh_forgejo_shim/git_recorder.py
@@
+TRACE_PATH_ENV = "FJ_SHIM_TRACE"
+REAL_GIT_ENV = "FJ_SHIM_REAL_GIT"
@@
+def create_git_recorder(
+    trace_path: str | Path,
+    *,
+    real_git: str | Path | None = None,
+    root_dir: str | Path | None = None,
+    wrapper_dir: str | Path | None = None,
+    env: Mapping[str, str] | None = None,
+) -> GitRecorder:
+    """Create a temporary ``git`` executable that records JSONL invocations."""

diff --git a/README.md b/README.md
@@
+## Codex Probe Diagnostics
+
+When Codex.app reports `GitHub CLI unavailable`, `Pull request status unavailable`, or stale branch data, enable tracing before guessing at the fix.
+
+```sh
+export FJ_SHIM_TRACE="$PWD/codex-probe-trace.jsonl"
+export FJ_SHIM_TRACE_BODY=1
+gfj trace summarize codex-probe-trace.jsonl
+```

Expected Impact for End-Users

Users and future agents can capture the exact command that caused a Codex.app compatibility state, summarize the likely failure class, and replay the local probe family before changing shim behavior. The default installed shim remains unchanged until tracing is explicitly enabled.

Validation

Issues, Limitations, and Mitigations

Follow-up Work