Suite went from 27 failures plus one permanent hang (never finished) to
1846 passed / 0 failed in ~85s, including under FORCE_COLOR.
- office_shutdown_lifecycle: construct WSHandler via the real __init__
(helper) instead of hand-copied __new__ stubs that drift from the
constructor (#11 added _runtime_status_sync_task and the stubs hung);
the formerly-hanging wait now has a 5s wait_for.
- import-time patch hygiene: company_recruiter / company_reorg /
engine_session_defaults replaced module-level permanent
tempfile.TemporaryDirectory monkeypatching with paired
setUpModule/tearDownModule, fixing order-dependent sqlite failures in
transcript_pagination during full runs.
- stale tests updated to current product semantics: resume stubs use
status="done" (failed is deliberately non-resumable), fix4 asserts the
native review contract through build_company_work_item_contract,
delivery fixture carries user_visible/feedback_scope=final, ownership
doc names progress_log, session compression calls
maybe_compact_session(force=True) explicitly, hard delete removes the
work item row, parallel-isolation asserts delegate rebind and stubs
_get_project_delegate, role update goes through OrgService on an
editable custom org (plus read-only rejection case), collab_rpc patches
the single os.name decision point instead of poisoning pathlib, codex
no-pty builds inside the patch, identity-guard false positives reworded.
- cli_board actions rewritten against the real OfficeServiceFactory seam
with a tempdir OPC_HOME (old direct-engine stubs were never consulted
and the tests wrote into the real OPC home).
- cli_app assertions strip ANSI via _plain_output so a color-forcing
shell (FORCE_COLOR) cannot break plain-text expectations.
- deleted never-runnable test_org_concurrency (pytest.mark.asyncio
without the plugin, stdlib-only assertions) and three dead skipped
filesystem-handoff tests.
- pyproject: dev extra (pytest, pytest-timeout) and a 300s per-test
timeout backstop so a wedged test fails instead of stalling the suite.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two user-visible defects in company mode, root-caused via project 6666/8888
DB forensics:
Progress-row flicker (thinking preview appearing/disappearing): progress
entries were broadcast to clients before reaching the persistence buffer,
so a tool_call-triggered session_detail snapshot rebuilt from the DB erased
freshly streamed entries from the live log. Buffer now fills before the
broadcast and session_detail flushes it before reading.
Native role transcripts incomplete (thinking only at start, no narration,
no final summary — external agents unaffected):
- thinking deltas shared one stream id per conversation turn while seq
reset per iteration, collapsing all iterations into one entry and
silently dropping live thinking from iteration 2 on; now keyed per
iteration like assistant deltas
- assistant_delta events were mapped to None; company mode now surfaces
them as streaming 'assistant' progress entries (rendered as Reply cards,
merged like thinking, excluded from inline chat rows)
- thinking was persisted one row per token, flooding the 1000-entry cap
and evicting interleaved tool history; append_progress now folds
streaming deltas per (type, turn, stream) with seq dedup
- the terminal company turn was hidden at summary detail and, worse, its
id-keyed backfill merge kept the first-inserted intermediate content, so
the final reply never reached any channel; terminal turns are now
flagged company_final_turn, visible at summary detail, and carry their
own ui_message_id so they insert as fresh rows
- appendProgressEntry applied its seq guard against unrelated entries when
the stream key was absent from the log, killing the first delta of any
fresh stream; the guard now only applies within the same stream
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collapse the dual permission stack into one policy. The runtime-side
ToolPermissionResolver (own safe lists, own grant memory, bypassed the
ApprovalEngine whenever it said ALLOW) is deleted; runtime_v2 now consults
ApprovalEngine.predict(), a synchronous fast path reading the same config
and the same persisted allowlist as the async authorize pipeline, so a
grant given anywhere is honored everywhere. permissions.py keeps only a
policy-free adapter; the duplicated permissions_v2 config fields and the
runtime grant persistence loop are removed (stale YAML keys are ignored).
New shell_safety module becomes the single source of truth for shell
classification: flag-audited read-only commands (awk/od/jq/sed -n/diff/
git subcommand table/... auto-allow; find -delete, sort -o, curl -o/-d,
rg --pre still prompt even when the bare name is config-listed),
keyword-aware compound splitting (loop/branch headers no longer poison
grants), expansion-safe $() handling, and fail-closed treatment of
anything unparseable or substitution-bearing.
Grant semantics are rebuilt around derived word-boundary prefixes:
"python3 -c" instead of token bags, interpreter -c/-m kept in the prefix,
bash/eval/sudo never grantable as prefixes, read-only segments exempt
from the every-candidate-must-match rule so a granted command chained
with ls/echo verification passes, and approve-once now records the exact
candidates as a session grant so identical re-runs stop re-prompting.
The authorize heuristic also audits the original command text instead of
the quote-dropping preview (echo "<EOF>" no longer reads as redirection).
Validated live on zz_perm_probe1 (native minimal org): awk/od/ls/cat/
sha256sum ran with zero cards, python3 -c parked once and three different
python3 -c commands then passed via the persisted prefix grant, and an
agent-issued rm -f compound correctly re-prompted showing only the
segments needing approval. Full suite failures are byte-identical to the
pre-change HEAD baseline (27 pre-existing).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Approval cards raised by company-mode internal scheduling turns (review/report
work items, session ids like `<root>:review::<wid>::vN`) were posted to the
turn's own session channel, which the UI deliberately hides. The card silently
timed out after 300s and the work item parked on AWAITING_HUMAN, so users saw
only the gate card and never the approval prompt. ws_handler now detects these
internal turns and routes their escalation cards to origin_task_id, the root
session's primary task channel, or the activity channel — never the hidden one.
Also unblocks the previously dead origin/session fallbacks in the resolver.
Unclassified LLM stream failures (e.g. provider content-filter rejections like
"input may contain sensitive information") used to hit a blind truncate-retry
loop that replayed the identical payload for a dozen-plus consecutive failures.
runtime_v2 now feeds the provider's verbatim error text back into the
conversation as a "[runtime notice]" system message so the model can adapt
(rephrase, drop quotes, change tack), bounded at 2 feedback retries (counter
resets on any successful stream) plus one context-reset attempt, then fails
honestly with the real error. The blind truncate path remains only for
classified tool-protocol errors.
Verified: new end-to-end tests for recover-after-notice and bounded-failure;
runtime_v2 + ws_handler + escalation/approval + company-mode suites all green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>