Fix HITL decisions missing phase and options - #960
Conversation
When agents create HITL decisions via egg-orch or egg-contract, the phase field was not being set, causing the egg-sdlc CLI to show 'unknown' phase, fail to find draft files, and fall back to generic menus instead of choice-specific options. Four-layer fix: - Auto-infer phase from pipeline state in DecisionQueue.queue_decision() - Add --phase and --decision-type CLI args to egg-orch decision create - Fallback to pipeline current_phase in sdlc_hitl.py display handler - Include phase in egg-contract add-decision contract entries
There was a problem hiding this comment.
Review: Fix HITL decisions missing phase and options
The approach is sound — auto-inferring phase at the server is the right place
to fix this, and the layered fallbacks (explicit → server-inferred → regex → API
fetch) are well-structured. However, there are a few issues to address.
1. --decision-type choices are incomplete (orch_cli.py)
File: sandbox/egg_lib/orch_cli.py, line ~1155
The CLI defines:
choices=["choice", "feedback"]
But the server accepts three valid values:
VALID_DECISION_TYPES = ("phase_gate", "choice", "feedback")(orchestrator/routes/decisions.py:172)
phase_gate is omitted from the CLI choices. While phase_gate decisions are
typically created programmatically by the orchestrator, omitting it from the CLI
makes the CLI an incomplete interface to the API. If someone needs to manually
create a phase gate for debugging or recovery, they can't.
Fix: Add "phase_gate" to the choices list, or document why it's intentionally
excluded (e.g., a help string saying "phase_gate decisions are created
automatically by the orchestrator").
2. Silent except Exception: pass swallows failures (sdlc_hitl.py)
File: sandbox/egg_lib/sdlc_hitl.py, lines 974–975
except Exception:
passThis catches everything — network errors, JSON decode failures, auth errors,
even KeyboardInterrupt (no, Python 3 Exception doesn't catch that, but it
does catch TimeoutError, ConnectionError, etc.). The bare pass means the
operator gets no signal that the fallback was attempted and failed.
This is a display-path fallback so it won't corrupt data, but at minimum log a
debug/warning so operators can diagnose why phase shows as "unknown" when they
expect it not to.
Suggested fix:
except Exception:
logger.debug("Failed to fetch pipeline phase for decision display", exc_info=True)Or if there's no logger in this module, at minimum use pass # noqa: ... with
a comment explaining the rationale.
3. No tests for any of the new behavior
The PR description says "All 256 existing tests pass" — but none of the four
changes have corresponding test coverage:
decision_queue.py: No test thatqueue_decision(phase=None)auto-infers
phase frompipeline.current_phase. This is the "highest-impact fix" per the
PR description, and it should have a test proving it works.sdlc_hitl.py: No test for the new API-fetch fallback when_detect_phase
returns "unknown".orch_cli.py: No test that--phaseand--decision-typeargs are wired
correctly into the request payload.contract_cli.py: No test thatargs.phasetakes precedence over
contract.get("current_phase"), or that the fallback works whenargs.phase
isNone.
These are all straightforward to test. The server-side auto-infer in particular
is the kind of thing that could silently regress if someone refactors
queue_decision later.
4. Minor: _detect_phase returns "analysis" mapping inconsistency
In sdlc_hitl.py, _detect_phase returns "refine" when it matches
\banalysis\b, which is correct (the phase enum value is "refine"). But
elsewhere in the same file (line 569), there's:
phase_label = "analysis" if phase == "refine" else phaseThis means the display says "analysis" but the phase value is "refine". The new
API-fetch fallback at line 971 will return "refine" from the pipeline state,
which will display as "refine" (not "analysis") because it bypasses the
_detect_phase path. This is consistent with how it already works when phase
is explicitly set on the decision, so it's not a regression — but it does mean
the display label will differ depending on how phase was determined. Not blocking,
just worth being aware of.
Summary
The core logic changes are correct and well-placed. The main gaps are:
- Missing
phase_gatein--decision-typechoices (minor) - Silent exception swallowing (minor, but affects debuggability)
- No test coverage for the new behavior (blocking — this is infrastructure code
that should have tests)
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
- Add phase_gate to --decision-type CLI choices in orch_cli.py - Replace silent except/pass with debug logging in sdlc_hitl.py - Add 16 tests covering: decision_queue auto-infer, sdlc_hitl API fallback, orch_cli arg wiring, contract_cli phase precedence
There was a problem hiding this comment.
All three actionable items addressed in 27083d7:
-
phase_gatein--decision-typechoices — Addedphase_gateto the choices list with a help string explaining it's typically created by the orchestrator but available for debugging/recovery. -
Silent
except Exception: pass— Addedlogging.getLogger(__name__)and replaced the barepasswithlogger.debug("Failed to fetch pipeline phase for decision display", exc_info=True). -
Test coverage — Added 16 tests across all four changed files:
test_decision_queue.py: 2 tests — auto-infer phase from pipeline, explicit phase not overriddentest_sdlc_hitl.py: 2 tests — API-fetch fallback on unknown phase, API failure stays unknowntest_orch_client.py: 5 tests —--phase/--decision-typearg parsing,phase_gateaccepted, payload wiring, omitted args absent from payloadtest_contract_cli.py: 4 tests —--phaseparsing, default None, explicit phase overrides contract, fallback to contract's current_phase
Re item #4 (display label inconsistency): Acknowledged, not a regression. The display will show the phase value as-is regardless of source, which is consistent with existing behavior when phase is explicitly set on the decision.
All 2568 tests pass, linter clean.
— Authored by egg
There was a problem hiding this comment.
Re-review: Fix HITL decisions missing phase and options
Reviewed delta: 025449e..27083d7 (commit "Address review feedback").
Previous feedback verification
All three actionable items from the initial review have been properly addressed:
-
phase_gatein--decision-typechoices — Added toorch_cli.py:1155with a clear help string explaining it's typically orchestrator-created but available for debugging/recovery. The choices now matchVALID_DECISION_TYPESon the server side (orchestrator/routes/decisions.py:172). -
Silent
except Exception: pass— Replaced withlogger.debug(...)withexc_info=Trueatsdlc_hitl.py:978. Logger is properly initialized at module level (sdlc_hitl.py:22). This provides the debug traceability requested. -
Test coverage — 16 tests added across four test files covering:
test_decision_queue.py: Auto-infer from pipeline + explicit phase not overriddentest_sdlc_hitl.py: API fallback on unknown phase + API failure stays unknowntest_orch_client.py: Arg parsing, payload wiring, omission behaviortest_contract_cli.py: Phase parsing, default None, explicit override, contract fallback
New code review
Production code changes are correct. Verified:
sdlc_hitl.py:973-974:pipeline_info.get("pipeline", pipeline_info).get("current_phase")correctly navigates theOrchClient.get_pipeline()return format ({"pipeline": {"current_phase": "..."}}).orch_cli.py:687-690: Phase and decision_type are conditionally added to the payload only when provided — omitted args don't send null values to the server.contract_cli.py:507:args.phase or contract.get("current_phase")correctly falls back. Note: ifargs.phaseisNoneandcontract.get("current_phase")is alsoNone,decision_phasewill beNone, which is fine — the server-side auto-infer indecision_queue.pyhandles it.- CLI
--phasechoices (["refine", "plan", "implement", "pr"]) matchPipelinePhaseenum values exactly.
Tests are well-structured. Verified:
- Mock return values match actual API response formats.
OrchestratorErroris correctly imported and instantiated with a single string arg (status_code defaults to 0).- The
capturing_make_requestpattern intest_contract_cli.pycorrectly capturesoriginal_make_requestbefore the patch context manager applies, so GET calls route to the mock server while POST payloads are captured for assertion. - Decision queue tests use the existing fixture's
_load_pipeline()/_save_pipeline()pattern consistently.
No issues found
The feedback has been thoroughly addressed. Code is correct, tests are meaningful and cover the right scenarios, and the changes integrate cleanly with the existing codebase patterns.
Advisory (non-blocking)
The test_orch_client.py file now has orch_cli tests appended to it (lines 528-614). These test egg_lib.orch_cli functions, not egg_lib.orch_client. A dedicated test_orch_cli.py would be more discoverable, but this is a naming/organization nit, not a correctness issue.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
| egg is addressing review feedback... |
|
egg feedback addressed. View run logs 3 previous review(s) hidden. |
The "refine" phase produces an "analysis" document, making the phase name inconsistent with the other phases (plan → plan, implement → implementation, pr → PR). Renaming to "analyze" aligns the phase name with its output and removes the need for special-case display label mapping that was flagged in PR #960 review. Changes across 62 files: - Rename PipelinePhase.REFINE → ANALYZE in all 3 enum definitions - Update JSON schemas, phase-permissions config, and CLI choices - Add backwards-compat Pydantic validators to normalize legacy "refine" values from stored contracts and pipeline state - Add gateway API normalization for "refine" → "analyze" - Preserve agent role names (REFINER, REVIEWER_REFINE) and contract field names (refine_review_cycles, refine_review_feedback) to avoid breaking stored data Authored-by: egg
Fix HITL decisions missing phase and options
When agents create HITL decisions during a pipeline (via
egg-orch decision createoregg-contract add-decision), the decisions were queued without aphasefield. This caused theegg-sdlcCLI to show "unknown" phase, failto locate draft files (e.g.,
943-unknown.mdinstead of943-analysis.md),and fall back to generic menus instead of choice-specific numbered options.
Four-layer fix applied across the stack:
Server-side auto-infer (
orchestrator/decision_queue.py): Whenphaseis None, auto-set it from the pipeline's current phase. Highest-impact fix
— all future decisions get the correct phase regardless of CLI source.
CLI args (
sandbox/egg_lib/orch_cli.py): Added--phaseand--decision-typearguments toegg-orch decision createfor explicitcontrol when needed.
Display fallback (
sandbox/egg_lib/sdlc_hitl.py): When the regexphase detector returns "unknown", fetch the pipeline's current phase from
the orchestrator API. Handles existing decisions created without phase.
Contract CLI (
sandbox/egg_lib/contract_cli.py): Added--phaseargto
egg-contract add-decisionand auto-populate from contract'scurrent_phasefield.Issue: none
Test plan:
egg-orch decision create --question "test?" --options "A" "B"and verify it gets the pipeline's current phaseegg-sdlcwatch loop shows proper phase and options for pending decisionsAuthored-by: egg