Repository navigation
Conversation
…ranscript speakers export_conversations(overwrite=True) recorded the resolved path but never checked it against the paths the active batch had already written, so two conversations whose date, title slug and 8-character id prefix agreed resolved to the same file: the second write replaced the first conversation and the function returned that path twice. The overwrite=False path already disambiguated, so the two branches disagreed about what overwrite may discard. Only names claimed by the current batch are disambiguated now, so a file left by an earlier run is still replaced in place. conversation_to_markdown also had three reporting defects on the same path: a non-string structured.category raised AttributeError instead of rendering, a boolean speaker rendered as Speaker False because isinstance(False, int) is true while an integer speaker under speaker_id, including speaker 0, was ignored, and a multi-line segment escaped its blockquote because the text was never flattened. A transcript carried under the segments key is accepted as well. Failure-Class: FC-batch-export-overwrites-a-same-batch-sibling Refs BasedHardware#23485
|
Verification, since fork pull requests wait in
The exporter imports only the standard library, so the suite needs nothing but Python and pytest; the package |
Git-on-my-level
left a comment
There was a problem hiding this comment.
Verdict: the code fix is correct, nicely scoped, and well-tested — one blocking one-line protocol issue in the PR body before this can merge.
Correctness (verified by execution):
- sdks/python-cli/examples/conversations_to_markdown.py — the export_conversations overwrite branch now disambiguates against used_paths before writing, mirroring the exclusive-create branch (both number from _2), so a same-batch collision can no longer silently discard a conversation while overwrite still replaces an earlier run's file in place. The str(category) coercion and the new speaker_label()/flatten_line() helpers (bool-vs-int, speaker 0, speaker_id fallback, name-beats-number, blockquote escape) all match the defects reported in #23485.
- sdks/python-cli/tests/test_conversations_to_markdown_filename_collisions.py — I ran the suite locally against this head: 20/20 pass; hermetic (importlib-loaded example, tempfile dirs, no network), and the colliding-pair test asserts two distinct files with both bodies intact.
- .github/failure-classes/FC-batch-export-overwrites-a-same-batch-sibling.json — schema-valid per the repo tooling (semantic id, empty evidence_prs explicitly allowed for a class born in this PR, scope hints resolve to tracked files).
Blocking (body edit only, no code change needed):
-
The body declares Failure-Class: FC-batch-export-overwrites-a-same-batch-sibling, but that class does not exist in the registry yet, and the FC- form is reserved for classes already registered on main. I ran the repo's own validator (scripts/failure-class validate) against this exact diff and body: it fails with instance_fix_mutates_registry, so Hygiene will go red once the fork workflows are approved. Changing the declaration line to:
Failure-Class: new
makes it pass (verified — this PR adds exactly one definition and no other registry edits, which is precisely what that form requires).
Notes:
- UX: batch exports stop losing conversations that share date/title/id-prefix; multi-line transcript text stays inside blockquotes; speaker labels render correctly (including speaker 0 and speaker_id-only exports).
- Blast radius: users of the python-cli example exporter only; output paths stay confined to the requested output dir (slugified title + sanitized id prefix, no traversal surface).
- Non-blocking nit: a list/dict structured.category now renders as its Python repr in the frontmatter; harmless, a join for lists would just read nicer someday.
USER IMPACT: batch exports to Markdown no longer silently drop a conversation when two share the same date and title, and transcripts render with correct speaker names, fully quoted.
Action items:
- Edit the PR body: replace the Failure-Class line with Failure-Class: new (one line, no push needed).
- After that this looks merge-ready pending fork-workflow approval and a green Hygiene run.
by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with need human response.
|
Applied the requested changes to 3 file(s). Ready for another look. |
|
Noting the overlap so the merge decision is easy: three pull requests now touch the same reported bug (#23486, #23496, #23502). This PR (#23496) carries the same-batch collision fix plus the three reporting defects that sit on the same function and are part of the same report:
Plus the If you would rather take the narrower collision-only fix from #23486 and fold these in separately, say so and I will reduce this to the delta. Otherwise this stands as the complete fix for the report. Either way the one thing worth keeping from this PR is the speaker/category/multiline coverage, since the collision fix alone leaves three reported defects open. |
|
Applied the requested changes to 1 file(s). Ready for another look. |
|
Applied the requested changes to 2 file(s). Ready for another look. |
|
Applied the requested changes to 1 file(s). Ready for another look. |
|
Applied the requested changes to 2 file(s). Ready for another look. |
|
Applied the requested changes to 1 file(s). Ready for another look. |
Git-on-my-level
left a comment
There was a problem hiding this comment.
Since my last review: addressed (the export_conversations restructure — disambiguation hoisted ahead of the overwrite/exclusive-create split — is behavior-equivalent and cleaner; the 20-test suite passes on this head, I ran it locally: 20/20) · still open (the PR-body Failure-Class declaration is unchanged) · newly introduced (items 2 and 3 below, one of them a behavior regression against main).
Verdict: this needs work before merge — the one-line body edit from my last review was not applied, and the follow-up commits broke the registry entry and a timestamp format.
-
PR body still declares
Failure-Class: FC-batch-export-overwrites-a-same-batch-sibling. That class is not in the registry on main, so the repo's validator (scripts/failure-class validate) still fails withunknown_failure_class. The fix is the one-line body edit from my last review:Failure-Class: new. No push needed. -
.github/failure-classes/FC-batch-export-overwrites-a-same-batch-sibling.jsonwas schema-valid at the last head; it is now invalid three ways:"id": "new"fails the requiredFC-<lower-kebab-slug>form, the filename no longer matches the id, and"status": "new"is not an allowed value (openordormant). Please restoreidtoFC-batch-export-overwrites-a-same-batch-siblingandstatusto"open". The literalnewbelongs only in the PR-body declaration, never inside the registry entry. -
sdks/python-cli/examples/conversations_to_markdown.py—format_timestampdropped its hours branch. Main rendersHH:MM:SSonce a segment passes one hour; this head renders wrapped minutes (format_timestamp(3661)returns"01:01"), while the docstring two lines up still says "MM:SS or HH:MM:SS format". Every transcript segment's start time goes through this function, so any conversation longer than an hour exports wrong timestamps. Please restore the hours branch and pin it with a small test — the new suite covers collisions and speaker labels but has no timestamp case. -
Smaller things: the
Failure-Class: newline added to the example's module docstring is inert (the validator reads the declaration from the PR body only) — please remove it from this user-facing example; and all three changed files lost their trailing newline at EOF.
Verified good on this head: the collision fix, speaker_label (bool-vs-int, speaker 0, speaker_id fallback, name-beats-number), flatten_line blockquote quoting, and the if lines and lines[-1] == ">" guard all match #23485, and sdks/python-cli/tests/test_conversations_to_markdown_filename_collisions.py passes 20/20 locally (hermetic, no network).
USER IMPACT: batch exports still stop losing conversations and transcripts render with correct speakers, fully quoted — but as-is this head would also print wrong timestamps for any conversation longer than an hour.
Action items:
- Edit the PR body:
Failure-Class: new(one line, no push). - Restore the failure-class JSON:
idback to the FC slug,statusback to"open". - Restore the
HH:MM:SSbranch informat_timestampand add a regression test for it. - Drop the docstring
Failure-Class:line and restore trailing newlines at EOF.
by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with need human response.
|
Applied the requested changes to 3 file(s). Ready for another look. |
|
All four items are addressed on head 1. PR body declaration — now reads 2. Registry entry — the current head already carries the correct entry: 3. 4. Smaller items — the Nothing else changed. The collision fix, |
|
Applied the requested changes to 3 file(s). Ready for another look. |
|
Applied the requested changes to 1 file(s). Ready for another look. |
What changed and why
Fixes the two defects reported in #23485, both in
sdks/python-cli/examples/conversations_to_markdown.py.1.
overwrite=Truediscarded a conversation from the same batch. The overwrite branch recorded the resolved path but never checked it against the paths the active batch had already written:Two conversations whose date, title slug and 8-character id prefix agree resolve to the same
base_name, so the second write replaced the first conversation,export_conversationsreturned that path twice, and the first conversation was gone with no error and no warning. Theoverwrite=Falsepath already disambiguated, so the two branches disagreed about whatoverwriteis allowed to discard. The fix disambiguates againstused_pathsbefore writing, so a name already claimed inside the batch is never reused, while a file left by an earlier run is still replaced in place, which is whatoverwritedocuments.2. Three reporting defects in
conversation_to_markdown.strip_surrogates(category)was called withoutstr(...)while the neighbouring fields already coerced, so a non-stringstructured.categoryraisedAttributeErrorand the whole conversation failed to export.isinstance(speaker, int), which is true forbool, so aspeaker: Falsesegment rendered as "Speaker False". An integer speaker underspeaker_id, including speaker0, was ignored because the value was read only fromspeaker, and there was no name fallback. The label now resolves a non-empty name fromspeakerorspeaker_namefirst, then a number fromspeakerorspeaker_id, excludingbool.>prefix without flattening, so a multi-line segment escaped its blockquote and the later lines rendered as ordinary paragraphs.A transcript carried under the
segmentskey is accepted as well;transcript_segmentsstill wins when both are present.Product invariants affected
none
How it was verified
The failure is a data-loss path in a batch export, so the verification is the regression suite written against the reported cases: two conversations that collide on the base name must produce two distinct files with both bodies intact, an earlier run's file must still be replaced in place, a non-string category must render, a boolean speaker must not become a number, speaker
0andspeaker_idmust resolve, and every transcript line must stay inside the blockquote.Fork pull requests sit in
action_requireduntil a maintainer approves the workflows, so the suite was also run on this exact branch outside that gate, in airdropia/earn-ops run 40083891023:python -m pytest tests/test_conversations_to_markdown_filename_collisions.py -q— all 20 tests passpython -m unittest discover -s tests -p test_conversations_to_markdown_filename_collisions.py—Ran 20 tests,OKspeaker_label({"speaker_id": 0}) == "Speaker 0",speaker_label({"speaker": False}) == "Speaker",speaker_label({"speaker_name": "Bob"}) == "Bob"andflatten_line("a\nb") == "a b"The example imports only the standard library, so the suite runs against the branch with nothing but Python and pytest; the package
conftest.pyis deliberately not loaded because it imports the CLI's HTTP stack.Tests
sdks/python-cli/tests/test_conversations_to_markdown_filename_collisions.pyadds 20 hermetic unit tests, no network and no real API, loading the exporter the way the sibling example tests do:overwrite=Falsekeeps disambiguatingsegmentskey is used whentranscript_segmentsis absent, and does not override it when both are presentUI evidence
Not applicable, this change touches the Markdown exporter output text only.
Failure class
Failure-Class: new
This adds exactly one definition file,
.github/failure-classes/FC-batch-export-overwrites-a-same-batch-sibling.json, whosecanonical_prevention_artifactis the new regression test.