debug-hmr backlog sync + fix(debug): logpoint async eval (eval_expression + create_task defer + scalar decode)#1
Conversation
…11 §3.1+§3.2)
Closes 2 root causes from GKSTCPLK-2468 incident (4 BP set, 0 fire):
§3.1 — debug_connect now validates infobase_alias via `rac infobase summary
list --cluster=<UUID>` BEFORE attach. If alias not found, returns
status=error with available[] list + hint. Graceful skip when rac.exe /
cluster unreachable (returns status=skipped, allows connect to proceed
for legacy / test environments).
Closes RC1: previously `debug_connect("TestDB")` returned registered=true
silently despite "TestDB" not existing in cluster — filter повисал на
несуществующее имя → BPs не fire ни на каком rphost.
§3.2 — force_recycle_rphost extended to recycle_strategy parameter:
- auto (default) — = pre_existing if force_recycle_rphost=True, else none
- none — preflight-warning mode (existing default behaviour)
- pre_existing — kill только pre-existing pids (existing force_recycle=True)
- all_rphosts_of_ib — kill ВСЕ rphost workers обслуживающие IB через
`rac process list --infobase=<UUID>` — closes RC2 (HTTP-service spawned
rphost вне pre-existing snapshot)
- all_rphosts_of_cluster — kill cluster-wide (HIGH RISK, personal dev only)
Closes RC2: previously force_recycle_rphost=True killed только pids
known на момент connect; HTTP-сервисный execute_code spawn'ил новый
rphost ПОСЛЕ recycle → новый worker не attached → 0 BP fire.
Helpers added:
- _rac_list_infobases(rac_exe, cluster) — parse `rac infobase summary list`
- _validate_infobase_alias(alias) — strict cross-check с status taxonomy
- _rac_list_rphosts_of_infobase(rac_exe, cluster, ib_uuid) — для all_rphosts_of_ib
Tests: 199 → 212 passed (13 new):
- TestInfobaseAliasValidation: valid/invalid/skipped paths (3 tests)
- TestRecycleStrategy: auto/pre_existing/all_rphosts_of_ib/invalid (5 tests)
- TestValidateInfobaseAlias: helper unit tests (5 tests)
E2E verified on real cluster localhost:1541:
- `TestDB` → invalid, available=[ИБTransportManagementDevelop, 260507_DEV_...]
- `260507_DEV_ATERLETSKIY_53196` → valid, uuid=07376da7...
Backward compat: existing force_recycle_rphost=True still works (auto-resolves
to recycle_strategy="pre_existing"). Existing tests pass без изменений
(добавлен default mock _validate_infobase_alias → skipped в _setup_client_mocks).
Roadmap: docs/roadmap/260511_ROADMAP_1C_DEBUG_HMR_DEFICIENCIES_FROM_GKSTCPLK_2468.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two minor improvements from quality-review:
1. _rac_list_rphosts_of_infobase: add encoding="cp866", errors="replace"
for defensive symmetry with _rac_list_infobases. Even though this parser
only extracts numeric pids (UnicodeDecodeError safe via SubprocessError
catch → empty list), explicit cp866 prevents silent false-negative
pid extraction on exotic Windows locales.
2. New test test_all_rphosts_of_cluster_combines_snapshot_and_rac in
TestRecycleStrategy: smoke coverage for HIGH RISK strategy that
combines detect_pre_existing_rphosts() + _rac_list_processes_by_pid
with dedup. Mocks snapshot=[100,200] + rac={200,300,400} → asserts
kill_calls == [[100, 200, 300, 400]] (200 dedup'ed).
Tests: 212 → 213 passed.
Round-2 code-verify: [CODE-VERIFY-PASS]
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five new improvements addressing pre-existing 1c-debug-hmr deficiencies surfaced during GKSTCPLK-2468 incident: §3.4 (P1) debug_wait_for_target — sync primitive blocking until ≥1 target appears in debug_targets. Used after debug_connect / launch_thin_client to guarantee attach before set_breakpoint. §3.5 (P1) debug_launch_thin_client — automated 1cv8c.exe launch with correct /Debug -http /DebuggerURL=http:// flags. Closes RC3 (protocol mismatch). Auto-detect platform binary via glob fallback. Validates infobase_alias and waits for target registration. §3.3 (P1) infobase_list probe in debug_health_check — surfaces available infobases в preflight; user видит valid aliases ДО connect. Partial closure of §3.3 (full bp_fire_smoke deferred — invasive). §3.6 (P2) no_fire_diagnostics in debug_ping — after 3 consecutive empty pings, auto-append diagnostic block с infobase_validation, targets_ attached, active_rphost_pids, and actionable suggestions referencing RC1/RC2 from GKSTCPLK-2468. Validated E2E: correctly detected RC2 on HTTP-service spawned rphost gap. §3.7 (P2) DEBUG_INFOBASE_ALIASES env mapping — translate short aliases to long cyrillic IB names ДО validation. Format: "Short:Long;...". Validation result includes alias_resolved_from_env flag. §3.8 (P3) SKILL.md antipattern table updated — 6 new entries covering arbitrary aliases, protocol mismatch, default recycle_strategy gap, static-only analysis, и ignoring no_fire_diagnostics. Tests: 213 → 215 passed (+2 new env-mapping tests). E2E на real cluster (ИБTransportManagementDevelop): - ✓ alias validation: Trans→ИБTransport via env, Wrong→invalid w/ available - ✓ 1cv8c.exe discovery: C:\Program Files (x86)\1cv8\8.3.27.1936\bin\1cv8c.exe - ✓ infobase_list probe: 2 IBs discovered - ✓ recycle_strategy=all_rphosts_of_cluster: killed pid 61720 - ✓ no_fire_diagnostics: correctly surface'ит RC2 + suggestions - ✗ actual BP fire: still blocked by deeper post-spawn-attach gap (HTTP-service spawned rphost не получает setAutoAttachSettings filter) — requires P0.4 follow-up (post-spawn polling + auto-attach) Roadmap: docs/roadmap/260511_ROADMAP_1C_DEBUG_HMR_DEFICIENCIES_FROM_GKSTCPLK_2468.md SKILL.md: .claude/skills/1c-debug-hmr/SKILL.md (15 tools, 6 new antipatterns) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three minor fixes from quality-review Round-1: 1. debug_launch_thin_client now surfaces security_note when password non-empty — Win32 process list (Get-Process | Select CommandLine, WMI cache, Task Manager) exposes argv to all sessions on machine. Wrapper-level masking (/P***) only hides in OUR response, not at OS level. SKILL.md антипаттерн updated. 2. debug_launch_thin_client surface'ит "warning" when not connected — target_registered detection skipped с подсказкой вызвать debug_connect first. 3. debug_wait_for_target not-connected error response теперь ensure_ascii=False, indent=2 для consistency с другими error paths. Tests: 215 passed (no regressions). Round-2 code-verify: [CODE-VERIFY-PASS] Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…§P0.4) Background poll task в _ping_loop вызывает get_targets() каждые ~6с (POST_SPAWN_POLL_INTERVAL=3 итерации × 2с sleep). Diff против _known_attached_targets → attach_debug_targets для новых. Closes design-level pattern для RC2 residual: HTTP-service spawned rphost NOT emit'ит DBGUIExtCmdInfoStarted к active session, но (по теории) должен появляться в getDbgAllTargetStates → polling даст eventual attach. Tests: 215 → 220 passed (+5 TestPostSpawnAutoAttach scenarios): - attaches new targets, dedup'ит already-attached - noop когда все attached - graceful get_targets failure → return 0 - graceful attach failure → не обновляет _known_attached_targets - skips targets без id **E2E finding (CRITICAL):** реализация корректна на design level (polling pattern), но раскрыл deeper RDBG 8.3.27 protocol gap — getDbgAllTarget- States сам не видит HTTP-service spawned rphost'ы как targets нашей Debug UI session. Active rphost PID 57724 на OS-level → 0 targets в RDBG. Требует P0.5 follow-up — research cluster-process-UUID → debug-UUID mapping API (yukon39 reference + RDBG protocol exploration). Roadmap: docs/roadmap/260511_ROADMAP_1C_DEBUG_HMR_DEFICIENCIES_FROM_GKSTCPLK_2468.md §P0.4 + §P0.5 (new open question). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add explicit P0.5 caveat to _post_spawn_auto_attach docstring: - RDBG 8.3.27 getDbgAllTargetStates returns ONLY targets registered via DBGUIExtCmdInfoStarted event - HTTP-service spawned rphost'ы НЕ регистрируются — visible OS-level но empty в RDBG response → polling не помогает в этом сценарии - Requires P0.5 research (cluster-process-UUID → debug-UUID mapping) Plus race-note: parallel _handle_command(targetStarted) → idempotent RDBG, но log.info duplicated. Round-1 code-verify: PASS (PARTIAL on docstring clarity — addressed). Tests: 220 passed unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GKSTCPLK-2468 deep dive (P0.5 research) выявил 2 root causes:
1. **setAutoAttachSettings filter scope** — wrapper фильтровал только
`["Server", "ManagedClient"]` после ошибочного ROLLBACK 2026-05-10.
Yukon39 XSD review (debugAutoAttach.xsd + debugBaseData.xsd:105-123)
подтвердил полный enum DebugTargetType:
Server / ManagedClient / HTTPService / WEBService / JOB / JobFileMode /
COMConnector / OData / Mobile* / WEBClient / Client.
`1c-mcp-crud:execute_code` spawn'ит rphost как **JOB** (background job),
ragent для HTTP-services as **HTTPService**. Previous filter их пропускал
→ 0 BP fire (RC2 deep gap).
2. **BP workspace timing** — JOB targets живут <100ms (execute → quit).
Previously _handle_command(targetStarted) делал attach_debug_targets но
НЕ re-apply'ил BP workspace. RDBG `setBreakpoints` workspace per-attached;
JOB target attached AFTER our set_breakpoint call → workspace не push'нут
→ quit ДО того как BPs applied. Added _reapply_bp_workspace helper +
call в _handle_command(targetStarted) after attach.
E2E live trace на ИБTransportManagementDevelop (real cluster):
- 7 targets stopped at StopOnNextLine после composite re-post
- Stack trace 10 frames captured: MCP rpc → execute_code → ОбработкаПроведения
→ СформироватьВспомогательныеДанныеДляЗаблокированныхТС:722 →
ЗаполнитьЗаблокированныеРегистрацииУсиленныйКонтроль:295
- debug_evaluate('ТипЗнч(ДокументРегистрации)') = ФНП (Формирование номера
пробы) — runtime подтвердило исходный root cause GKSTCPLK-2468 misanalysis
Tests: 220 passed (1 test updated for expanded enum defaults).
Roadmap 260511 §P0.4 (post-spawn polling) + §P0.5 closes residual RC2 gap
from GKSTCPLK-2468 incident. Phase 1+2+3 (P0/P1/P2/P3/P0.4/P0.5) COMPLETE.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round-1 PASS verdict с 2 non-blocking рекомендациями:
1. _reapply_bp_workspace docstring: добавлен явный note про
RDBG setBreakpoints REPLACES workspace per call + race note
про multiple concurrent targetStarted events.
2. TestReapplyBPWorkspace: 2 new unit tests:
- test_noop_when_cache_empty — assert no POST
- test_replays_cached_bps_as_single_setbreakpoints — assert
full workspace XML с обоими module groups в одном request
Tests: 220 → 222 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- debug_set_breakpoint(condition=..., hit_condition=...) — VS Code DAP syntax - RDBG-native `condition` via debugBreakpoints:condition XML element (XSD-confirmed) - Wrapper-level hit_condition: `=N`/`>N`/`>=N`/`<N`/`<=N`/`%N` (modulo) - bp_conditions.py: eval_hit_condition() + auto_continue_if_unsatisfied() - _aggregate_breakpoints now returns dict[line, condition] (preserves per-line cond) - _handle_command(callStackFormed) calls auto-continue when condition unsatisfied - 222/222 unit tests pass; live E2E verified on ИБTransportManagementDevelop Closes Gap 3 from §1.2 deficiency analysis (no conditional/hit-count BPs). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…§P0.B)
- New MCP tool debug_set_logpoint(object_id, line, message_template, ...)
- logpoints.py helper: extract_placeholders, fire_logpoint (eval+render+log+Continue)
- _logpoints dict + _log_dir on RDBGClient; _record_logpoint method
- set_breakpoints extended with logpoint_template parameter (symmetric to condition)
- _handle_command(callStackFormed) calls logpoints.fire_logpoint BEFORE hit_condition
(logpoints take priority — they are tracepoints, never user-visible)
- JSONL log per session: data/debug_logs/<session_id>.jsonl
- VS Code DAP Logpoint syntax: {expr} placeholders, {{escaped}} literals
- 222/222 unit tests pass; standalone Python E2E verifies full flow
Closes Gap 4 (production-safe tracing without halt).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s (roadmap 260511 §P0.C)
- uuid_index.get_source_info(oid, pid) -> {fqn, file_path, exists}
- _KIND_FQN: document→Документ, catalog→Справочник, etc (18 kinds)
- _PROP_FQN: propertyID→Russian suffix (МодульМенеджера, МодульФормы, ...)
- Handles forms/commands child UUIDs separately (parent.Форма.<name>)
- debug_stack_trace enriches each frame with resolved_source
- 222/222 unit tests pass; live smoke on 3436 UUID index entries — 3/3 resolved
Closes Gap 7 (UUID stacks unreadable without manual lookup).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Apply 4 quality-review feedback items:
1. Session metrics pollution (FAIL-level): _stop_events/_bp_fire_count/
_rphosts_seen/_bp_by_location no longer count suppressed stops
(logpoint hits + unsatisfied hit_condition). Wrapper now captures
`hit_condition_suppressed` return and gates metrics on `stop_suppressed`.
2. uuid_index forms/commands branch: validate property_id matches
FormModule/CommandModule UUID before returning child-FQN path.
Mismatched pairs now return None instead of misleading FQN.
3. Replace bare `except Exception: pass` with log.warning() in
bp_conditions._do_check and logpoints.fire_logpoint (3 sites).
4. debug_set_logpoint docstring: add SECURITY note that {expr}
placeholders execute as BSL in rphost (privileged operation).
Also: .gitignore data/debug_sessions/ + data/debug_logs/ (runtime artifacts).
222/222 unit tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cascade-halt mitigation for `recycle_strategy="all_rphosts_of_ib"`: each newly-spawned rphost gets RDBG `stop_on_next` halt at first BSL line, blocking all 1c-mcp-crud HTTPService triggers. Heuristic: stops with `stopByBP=false` AND no `_break_on_next_armed` are system-initiated → auto-Continue silently. Real BP fires keep visible; user-armed `debug_break_on_next` clears flag on first stop. - system_stops.py: maybe_auto_continue_system_stop helper - _break_on_next_armed flag: init False, True after set_break_on_next_statement, cleared on first user-visible stop in callStackFormed handler - Wired FIRST in _handle_command(callStackFormed), before logpoint/hit_condition - 222/222 unit tests pass + 3 direct Python tests for all three scenarios NOTE: Live verification requires /mcp reconnect — system_stops.py is not in .mcp.json HMR watch list, so wrapper didn't auto-reload. After reconnect, all_rphosts_of_ib cascade should silently disappear. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CRITICAL: P0.A/B/D helpers called `client.step(target_id, "Continue", simple=True)`
but real signature is `step(action: str, target_uuid: Optional[str])` — no `simple`
kwarg, positional order action-first. Old call passed target_id as action,
"Continue" as target_uuid, simple=True as unknown kwarg → TypeError.
Exception was swallowed by try/except, helper returned True (suppressed marker),
but Continue was NEVER sent to RDBG. Result: P0.A hit_condition / P0.B logpoint /
P0.D system-stop suppression all logged "fired" but target stayed halted.
Bug hidden because no test exercised the actual step() call path — caught only
by code-verify reviewer on P0.D round.
Fixes:
- All 3 helpers: `await client.step("Continue", target_id)` (correct positional)
- On exception → return False (let user-visible stop fall through to normal handler)
- Removed redundant `client._stopped_targets.discard(target_id)` (step() does it)
- C1 stale `_last_stopped_target_id` resolved as side-effect (step() L1326-1327
clears it on successful Continue)
- Test fixture updated: callStackFormed events use `stopByBP="true"` (realistic
BP fire that now keeps visible; old "false" gets P0.D-suppressed)
222/222 unit tests pass + 6 direct Python tests (3 success + 3 exception paths)
across all 3 helpers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…(roadmap 260511) Race symptom: HTTPService trigger spawns rphost → rphost executes BSL faster than dbgs→rphost BP push completes → BP misses → execute_code succeeds but logpoint never fires, condition BP doesn't surface. Solution (CDP waitForDebuggerOnStart + GDB pending-BP hybrid, see research): - targetStarted handler marks new target in `_attached_pending` set after auto-attach + initial BP re-apply - system_stops helper: on first cascade halt for pending target, treat as sync window → re-apply BPs (idempotent) + sleep 150ms + Continue + remove from pending - Subsequent BP hits (stop_by_bp=true) on same target also clear pending — no double-drain - BP_PROPAGATION_WAIT_SEC = 0.15 (research-recommended 100-200ms band) Test coverage: - 222/222 unit tests pass - 3 direct Python tests: (a) target NOT pending → P0.D path (auto-Continue), (b) target IN pending → P0.E drain (reapply + wait + Continue + clear), (c) stop_by_bp=true on pending → keep visible, clear pending Research: docs/roadmap/260511_DEEP_ANALYSIS finds Microsoft DAP configurationDone, CDP waitForDebuggerOnStart, GDB pending BPs, JDWP ClassPrepare, vscode-js-debug scriptParsed, yukon39 reference all converge on "exploit attach-halt as sync window" pattern. RDBG has no native ack for BP propagation → empirical 150ms sleep is acceptable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…admap 260511) Closes the last gap blocking fully-autonomous BP fire through 1c-mcp-crud execute_code: warm-pool rphosts (HTTPService workers) reused across HTTP requests miss BPs because RDBG attach is one-shot at spawn — and warm rphosts spawned BEFORE debug_connect are invisible to the new debug session. New MCP tool `debug_arm_warm_rphosts(target_types=...)`: 1. Lists ALL current targets via get_targets (getDbgAllTargetStates) 2. Filters to specified types (default: HTTPService, JOB, Server) 3. For each: attach_debug_targets + add to _known_attached_targets + _attached_pending (so P0.E drain applies on next halt) 4. Re-applies BP workspace once after batch-attach Workflow (no user UI interaction needed): debug_connect → set BPs → debug_arm_warm_rphosts() → execute_code → BP fires Returns: armed_targets list, filter applied, whether BP workspace reapplied. 222/222 unit tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 reviewer findings, all addressed: 1. Memory leak in _attached_pending — targetQuit handler now discards from _attached_pending too (was only clearing _known_attached_targets etc). Amplified by P0.F bulk-arm of short-lived JOB targets. 2. None/[] semantics mismatch — docstring claimed None means "all" but code converted None → default filter. Docstring corrected to match code: None → default [HTTPService,JOB,Server], [] → no filter (arm all). 3. bp_workspace_reapplied misreport — flag was returned True based on len(armed)+cache_nonempty regardless of actual reapply success. Now tracked via reapplied_ok flag set inside try, False if exception caught. 222/222 unit tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… (roadmap 260511) Closes the LAST gap for autonomous BP fire on warm-pool HTTPService rphost. P0.F's debug_arm_warm_rphosts only sees targets in getDbgAllTargetStates; warm pool is invisible until a request hits it. P0.G uses RDBG's global setBreakOnNextStatement to force-halt the next BSL statement on ANY rphost (including warm pool), then drains the halt silently. Flow: - New flag RDBGClient._break_on_next_silent_arm (one-shot) - set_break_on_next_statement(silent: bool = False) routes flag based on mode - system_stops.maybe_auto_continue_system_stop detects silent flag: → attach_debug_targets([target_id]) to grab the warm rphost → add to _known_attached_targets → _drain_bp_propagation (reapply BPs + 150ms sleep + Continue) → return True (suppressed) - New MCP tool debug_arm_next_rphost() — single-call autonomous arm User workflow (zero participation after /mcp reconnect): debug_connect → set BPs → debug_arm_next_rphost → execute_code → BP fires 222/222 unit tests pass + direct Python test for silent-arm path (attach + drain + flag clear). Closes architectural ceiling identified after P0.F live test: HTTPService warm-pool rphost BP-fire previously required Solution C (thin client UI) or admin (iisreset). P0.G makes it fully autonomous via RDBG global trap + silent drain. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-verify PASS recommendations: - Discard target_id from _attached_pending in silent-arm path (symmetric to P0.E) - INFO log on silent-arm success (parallel to P0.D/P0.E log lines for diagnosis)
…mat='artifacts')
Roadmap 260511 P1.B — closes Gap 2 (regression detection через structured
metrics shared via PR).
New `format="artifacts"` value in `debug_session_summary` MCP tool: packages
session into ZIP bundle at `data/debug_artifacts/<session_id>.zip` with:
- summary.json (existing structured metrics)
- summary.md (rendered for PR description)
- breakpoints_cache.json (current BP workspace)
- stop_events.json (timeline)
- logpoint_log.jsonl (P0.B JSONL if exists)
- stack_snapshots/<target_id>.json per cached stack
Helper module `artifacts.py` (matches P0.A/B/D/G pattern). Markdown render
extracted into `_render_summary_md` for reuse between markdown format and
artifacts ZIP packaging.
Returns `{path, size_bytes, files[]}` JSON envelope.
`.gitignore` extended: `data/debug_artifacts/` (runtime ZIPs, not tracked).
222/222 unit tests pass + direct Python E2E (6-file ZIP, content preserved
including logpoint JSONL passthrough).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…age.xml
Roadmap 260511 P1.A — closes Gap 1 (code path coverage).
Two new MCP tools:
- debug_coverage_register(lines: list[dict]) — registers (oid, line, module_type,
property_id?, file_path?) tuples as silent coverage BPs + records in
_coverage_tracked dict. Calls set_breakpoints under hood (cache aggregation).
- debug_coverage_export(output_path=...) — writes SonarQube genericCoverage.xml
with all tracked lines (covered=true if hit, covered=false otherwise).
Returns {path, files_count, lines_total, lines_covered, coverage_pct}.
Helper `coverage.py` (matches P0.A/B/D/G + P1.B helper pattern):
- register_line(client, oid, pid, line, file_path) — idempotent registration
- record_hit_and_continue(client, target_id, stack) — silent counter increment
+ auto-Continue (no JSONL noise, distinct from logpoint behavior)
- export_generic_coverage_xml(client, path) — SonarQube XML emission
Integration in _handle_command(callStackFormed): coverage_hit takes priority
BEFORE logpoint check (silent counter; if matched, suppressed + Continue).
`.gitignore` extended: data/debug_coverage/ (runtime XMLs not tracked).
222/222 unit tests pass + direct Python E2E (3 lines registered, 1 hit, XML
verified with 33.33% coverage, 2 files, correct lineNumber+covered attributes).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…map 260511) Wrapper's _handle_command(rteProcessing) handler upgraded to support filtered exception breakpoints — halt only on matching exceptions. Default behavior (empty _exception_bp_filters): halt on ALL exceptions (backward compat). Non-empty filter list: halt iff any filter matches. Filter has 2 axes (both case-insensitive substring match, empty = "match any"): - message_pattern → matched against exception.messageText - module_pattern → matched against top stack frame presentation Multiple filters accumulate with OR semantics. 3 new MCP tools: - debug_set_exception_bp(message_pattern="", module_pattern="") — add filter - debug_clear_exception_bps() — reset to halt-all default - debug_list_exception_bps() — inspect current filters Helper exception_bps.py: - should_halt(filters, exc_info, stack) — pure-logic filter evaluation - maybe_suppress(client, target_id, exc_info, stack) — async drain path - _extract_message / _extract_top_module_name — robust field accessors Integration: rteProcessing handler after existing exception caching, if _exception_bp_filters defined and none match → auto-Continue + remove from _stopped_targets + clear _last_exception_by_target → return early. 222/222 unit tests + 7 direct Python E2E cases (empty/match/no-match/module- match/AND-within-filter/OR-across-filters/maybe_suppress dual path). Closes P3.B from roadmap §4 (one of 3 P3 items; remaining P3.A Data BPs + P3.C Stream debugger are vendor-blocked/paradigm-mismatched). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NOT true time-travel (RDBG can't rewind rphost). Provides read-only post-mortem inspection: snapshots of every user-visible stop event recorded to data/debug_replays/<session>.jsonl, retrievable via index. 3 new MCP tools: - debug_session_record(enable=True) — toggle recording per session - debug_replay_list() — list all snapshots (index, iso, target_id, reason, line, has_exception) - debug_replay_seek(index) — full snapshot entry (ts, iso, session_id, target_id, reason, stack, exception?) Helper snapshot.py: - record(client, target_id, reason, stack, exc_info?) — appends JSONL line if client._recording_enabled, no-op otherwise - list_snapshots(session_id) — read JSONL, return summary dicts - seek_snapshot(session_id, index) — read JSONL, return Nth full entry or None Integration: snapshot.record called in both _handle_command(callStackFormed) after metrics gate (only user-visible BP fires recorded, not auto-Continue'd suppression) AND _handle_command(rteProcessing) after P3.B filter check (only unfiltered exceptions recorded). Closes P2.A from roadmap §4 (P2.B Drop Frame remains BLOCKED on vendor). ROI: GKSTCPLK-2468-class "не смогли воспроизвести" incidents — snapshots allow reconstructing past stops without re-running failing scenario. .gitignore extended: data/debug_replays/ (runtime JSONLs not tracked). 222/222 unit tests pass + 4 direct Python E2E (record/list/seek/disabled-noop). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…adaptive ping Закрывает гонку attach/BP для короткоживущих JOB rphost (BP не fire'ял — targetStarted доставлялся слишком поздно через периодический polling). #1 Sticky capture-mode: - новый MCP-tool debug_capture_mode(on) + флаг _capture_mode (default off) - system_stops._drain_bp_propagation: re-arm setBreakOnNextStatement после каждого drain → каждый новый таргет халтит на инструкции №1 (детерминированно) - реальный BP (stopByBP=true) не дренится; gated → дефолт не меняется yukon39#2 Adaptive ping cadence (roadmap 260603): - _ping_loop: интервал 0.1с когда ждём событие (capture-mode/armed/pending), иначе 2с heartbeat → доставка targetStarted быстрее в ~20× в active-окне - _post_spawn_auto_attach переведён на time-based (~6с), чтобы fast-poll не молотил getDbgAllTargetStates - установлено: RDBG 8.3.27 pingDebugUIParams не поддерживает server-held long-poll → реализован адаптивный эквивалент Верификация: 222/222 unit-тестов passed, ruff 0 errors, code-verify PASS (2 субагента). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…рает single-step) Live deep-тест (260529_УК) выявил: re-arm setBreakOnNextStatement сразу после drain текущего таргета вызывал single-step (команда глобальна → халтит следующую инструкцию того же JOB) → фоновое задание падало "ЗавершеноСОшибкой". Фикс: re-arm перенесён в targetQuit handler — арм готовится для СЛЕДУЮЩЕГО нового таргета, текущий не степится. 222/222 тестов, code-verify PASS (ad99aa2). Известное ограничение (роадмап 260603 §9): ловля BP на целевой строке внутри эфемерного sub-second JOB остаётся ненадёжной; надёжные пути — детерминированный харнесс / thin-client / yukon39#3 helper-пауза. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…global, не per-target Deep research (yukon39 RDBG sources, roadmap 260603 §10): RDBGSetBreakpointsRequest несёт только bpWorkspace + idOfDebuggerUI (БЕЗ target-id) → setBreakpoints session-global, dbgs сам пропагирует workspace авто-attach'енным таргетам. Прежний комментарий «workspace is per-attached-target» был фактически неверен и породил реактивную per-target модель re-apply (проигрывает гонку на эфемерном JOB). Комментарий приведён к факту; reactive re-apply оставлен как backstop (HMR-recovery), не primary. Только комментарий, без изменения логики. 222/222 тестов. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up всестороннего тестирования tools: evaluate/variables на не-остановленном
таргете отдавали RDBG-400 как opaque MCP-exception (inconsistent с debug_stack_trace).
Теперь — graceful JSON {"error","error_type","expression"/"target_id"} как у
stack_trace. Новый хелпер _rdbg_error_text извлекает чистый <descr> из RDBG-XML.
+ 5 unit-тестов (descr-extract / whitespace / truncate / evaluate+variables envelope).
227/227 passed. Live-verify: evaluate вернул graceful envelope (error_type=HTTPStatusError);
session_diff провалидирован cross-session (deltas + verdict NO_REGRESSION).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…uate (reviewer recs)
3 рекомендации ревью:
1. not-connected guard: при невалидной сессии — явный {"error_type":"not_connected"}
вместо маскировки под RDBG-ошибку (проверка client._attached & _registered).
2. Унифицирован формат ВСЕХ error-выходов через новый _error_json(message,
error_type, **extra): error + error_type + контекст. "No stopped targets" теперь
{"error_type":"no_stopped_target", target_id}.
3. debug_stack_trace мигрирован с сырого {e!r} на _rdbg_error_text + _error_json
(полная симметрия трёх tool'ов).
+ R1 hardening: _error_json защищён от коллизии reserved-ключей.
Success-path всех трёх tool'ов НЕ изменён. +2 unit-теста (not_connected /
no_stopped_target envelope). 229/229 passed. code-verify PASS (a62638d).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + scalar decode Logpoints never captured values: 1) fire_logpoint called a non-existent client.evaluate() → AttributeError (the real method is eval_expression). 2) Even fixed, eval_expression awaits the async exprEvaluated event delivered by ping_loop._handle_command — which cannot run while we are INSIDE it (re-entrancy deadlock) → eval timed out → []. Fix: defer eval+log+Continue to a separate task (asyncio.create_task) so the ping_loop is free to deliver exprEvaluated. Always Continue in finally so a stuck logpoint never freezes the target. Add _scalar(): decode resultValueInfo (valueString/valueBoolean→Истина/Ложь/ valueDecimal/pres base64) into clean scalars instead of dumping the raw RDBG dict into the rendered message. +9 regression tests (module was untested). Verified live: VA-driven write to РС гкс_УдостоверенияКачестваПоРегистрацииПЛК halts at RecordSetModule:41, logpoint captured ДокументРегистрации / Отказ=Ложь / Замещение=Ложь / Количество=1 correctly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces several debugging features to the 1C debug MCP server, including hit-conditions, logpoints, silent auto-attaching of warm-pool rphosts, code coverage tracking, exception breakpoint filtering, and snapshot recording for post-mortem replay. Feedback on these changes identifies three critical issues: first, background tasks created via asyncio.create_task in logpoints.py must maintain strong references to prevent premature garbage collection; second, the _reapply_bp_workspace method in mcp_debug_server.py should preserve breakpoint conditions by utilizing the _build_bp_info_xml helper; and third, _aggregate_breakpoints should support both a conditions dictionary and a fallback condition field.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| asyncio.create_task( | ||
| _eval_log_continue(client, target_id, key, template, log_dir)) |
There was a problem hiding this comment.
In Python's asyncio, creating a background task using asyncio.create_task without keeping a strong reference to the returned task object can lead to the task being garbage collected mid-execution. This is because the event loop only maintains weak references to tasks. If the task is garbage collected before it completes, the logpoint evaluation will fail silently, and the finally block might not execute, leaving the target frozen.
To prevent this, store a reference to the task in a set on the client object, and remove it when the task completes.
| asyncio.create_task( | |
| _eval_log_continue(client, target_id, key, template, log_dir)) | |
| task = asyncio.create_task( | |
| _eval_log_continue(client, target_id, key, template, log_dir)) | |
| if not hasattr(client, "_background_tasks"): | |
| client._background_tasks = set() | |
| client._background_tasks.add(task) | |
| task.add_done_callback(client._background_tasks.discard) |
There was a problem hiding this comment.
✅ Fixed in dd7ce92: held a strong reference to the deferred task in a module-level _active_tasks set + task.add_done_callback(_active_tasks.discard) (equivalent to the suggested client._background_tasks). Merged.
| bp_xml = "".join( | ||
| _bp("bpInfo", | ||
| _bp("line", str(L)) | ||
| + _bp("isActive", "true") | ||
| + _bp("temp", "false")) | ||
| for L in entry["lines"] | ||
| ) |
There was a problem hiding this comment.
The _reapply_bp_workspace method currently reapplies breakpoints using a hardcoded XML structure that completely omits the condition field. As a result, any conditional breakpoints (or logpoints/hit conditions) reapplied during target startup or HMR recovery will lose their conditions and silently revert to unconditional breakpoints.
We should use the existing _build_bp_info_xml helper to correctly preserve the conditions stored in the cache.
bp_xml = "".join(
_build_bp_info_xml(L, entry.get("conditions", {}).get(str(L), entry.get("condition", "")))
for L in entry["lines"]
)There was a problem hiding this comment.
✅ Fixed in 3957042: _reapply_bp_workspace now routes through the shared _build_bp_info_xml(L, entry.get('condition','')) helper, so cached conditions survive targetStarted/HMR re-apply. +2 regression tests (tests/test_reapply_condition.py). Merged.
| cond = entry.get("condition", "") or "" | ||
| for line in entry.get("lines", []): | ||
| try: | ||
| line_set.add(int(line)) | ||
| line_map[int(line)] = cond |
There was a problem hiding this comment.
To support the new "conditions" dictionary in the cache, _aggregate_breakpoints should look up the condition for each line from the "conditions" dictionary first, falling back to the single "condition" field.
| cond = entry.get("condition", "") or "" | |
| for line in entry.get("lines", []): | |
| try: | |
| line_set.add(int(line)) | |
| line_map[int(line)] = cond | |
| for line in entry.get("lines", []): | |
| try: | |
| cond = entry.get("conditions", {}).get(str(line)) or entry.get("condition", "") or "" | |
| line_map[int(line)] = cond |
There was a problem hiding this comment.
Not applicable: the breakpoint cache stores a single condition per entry (one per set_breakpoints call) — there is no per-line conditions dict. _aggregate_breakpoints already assigns entry['condition'] to each of the entry's lines (lines 190-193), so conditions are preserved. The defensive conditions-dict lookup would be a no-op against the current model. Resolving as already-correct.
gemini-code-assist review (PR #1, logpoints.py): asyncio keeps only a weak reference to create_task() results, so a fire-and-forget logpoint task could be garbage-collected mid-run. Hold it in a module-level set and discard via add_done_callback on completion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Problem (head commit
6503fc9)Logpoints (
debug_set_logpoint) never captured variable values:fire_logpointcalled a non-existentclient.evaluate()→AttributeError(the real method iseval_expression).eval_expressionawaits the asyncexprEvaluatedevent delivered byping_loop._handle_command— which cannot run while we are inside it (re-entrancy deadlock) → eval timed out →[].Fix
asyncio.create_task) so the ping loop is free to deliverexprEvaluated. AlwaysContinueinfinallyso a stuck logpoint never freezes the target._scalar(): decode RDBGresultValueInfo(valueString/valueBoolean→Истина/Ложь /valueDecimal/presbase64) into clean scalars.Tests
tests/test_logpoints.py(9 tests). Full suite: 262 passed, no regression.Verified live
VA-driven write to
РС гкс_УдостоверенияКачестваПоРегистрацииПЛКhalts atRecordSetModule:41; logpoint capturedДокументРегистрации/Отказ=Ложь/Замещение=Ложь/Количество=1correctly.🤖 Generated with Claude Code