When a Jira issue disappears, should its memory disappear too?
What we learned building a Jira connector for Cognee: synthetic test data, careful deletion semantics, and the bugs that appeared only after we kept reviewing.
A Jira connector sounds like a data plumbing task: read issues, comments, and attachments, turn them into documents, and make them searchable. The harder question came later: when Jira stops returning something, should Cognee forget it?
That question turned a small connector project into a lesson in uncertainty, test design, and why “all tests pass” is a milestone—not a guarantee.
Start with data we could safely break
I created a free Jira Cloud test site and a tiny synthetic project: two issues about Orion retry limits and Vega queue limits, with comments and a text attachment. The content was invented so we could know exactly what the connector should find, edit, and remove without bringing real work data into an early test.
The first sync produced five documents: two issues, two comments, and one attachment. On an unchanged sync, the documents remained while the connector reused the issue descriptions. Comments and attachment inventories were refreshed independently. A comment can change without a useful parent-issue timestamp change, so relying on the parent cursor alone could leave old words behind.
We checked the resulting memory in Cognee’s Data records, graph, and six vector collections. Search results alone are not enough evidence that removed content has actually been cleaned up.
Here is the basic path we wanted to verify:
Jira issues ─┬─> issue documents ─┐
├─> comment documents ├─> complete replacement snapshot ─> Cognee
└─> attachment text ──┘
│
└─ empty snapshot ─> remove old corpus
“Missing” is not the same as “deleted”
An issue can disappear from a query because it was deleted. It can also move, stop matching a JQL filter, or become hidden after an access change. Jira search is eventually consistent, too. From a missing result alone, the connector cannot know which explanation is right.
We made that tradeoff explicit in the API. Callers must choose a policy:
-
mirror_visibilityreplaces the corpus with what the account can currently see. It supports next-sync forgetting when access stays complete and stable. -
require_confirmationpauses when known documents disappear, so the caller can check the situation and acknowledge specific removals.
Neither policy turns absence into proof of deletion. Visibility mirroring can remove content after access changes; confirmation can delay a real deletion. The choice belongs in configuration and documentation, not hidden behind a default.
Reviews changed our understanding
The first strict review rounds found problems that happy-path tests had missed. Our main example selected the guarded policy, so it did not demonstrate automatic next-sync removal. An empty snapshot also needed to create a physical staging table; otherwise, after dlt staging state was lost, Cognee could retain old Data records. And our renderer flattened superscript and subscript, changing the meaning of expressions such as 10⁻³.
Fixing those issues was not the end. A later review modeled Jira’s general search returning stale membership while reconciliation returned the current result for specific issue IDs. We were adding reconciled results to the general list but ignoring IDs excluded by reconciliation. That kept an issue’s documents even after it stopped matching the selected JQL filter.
Our first correction removed the issue from the current list. A repeat run exposed a second bug: stale general search could reintroduce it. The final logic reconciles both retained and newly discovered IDs in batches, then lets each complete reconciliation batch replace general search membership for those IDs. This prevents a known filter exit from being resurrected while leaving unknown discovery subject to eventual consistency.
Another review caught a usability bug: a raw ORDER BY regular expression rejected a valid filter such as summary ~ "order by". We changed the check to distinguish quoted JQL values from a real ordering clause.
The review-and-fix loop looked like this:
Review finding
↓
Small reproduction
↓
Fix and add regression test
↓
Strict review again
↓
New edge case? ── yes ──> repeat
│
no
↓
Rebuild and verify the exact package
We also found two test-harness failures along the way. One subprocess imported an older installed package instead of the source under test. A global HTTP mock counted unrelated telemetry requests as Jira retries. Both taught us to verify what a test actually loaded and intercepted, rather than trust the test’s name or shell exit code.
| What failed | What we changed | The lesson |
|---|---|---|
| Default workflow did not forget automatically | Require an explicit removal policy; make the main example choose visibility mirroring | Make important behavior visible in the API |
| Empty rebuilt staging left old Cognee records | Materialize the empty resource table | Test empty results and recovery, not only populated syncs |
| Superscript/subscript lost meaning | Preserve the notation and invalidate old cached rendering | Text conversion can change meaning, not just formatting |
| Stale general search retained and later resurrected filtered issues | Reconcile retained and discovered IDs; replace membership per completed batch | Test repeated runs after the first fix |
| Quoted “order by” text was rejected | Detect ordering clauses outside quoted strings | Validate query structure, not keyword appearances |
| Subprocess tested an old install; HTTP mock counted telemetry | Bind subprocesses to the imported package and scope the mock to the Jira client | Check the test harness itself |
What we verified—and what remains unknown
The final local run passed 69 tests on each of Python 3.11, 3.12, and 3.13. We also tested an authenticated synthetic Jira lifecycle, recovery after downstream ingestion failure, and cleanup after staging was rebuilt. With two overlapping corpora, we removed one corpus’s facts while preserving the other’s, then removed the remaining facts when its owner retired them. We inspected Data, graph, and vector stores through the cleanup steps.
An independent GPT-6.1 Sol High review found further defects in two later rounds. After the fixes, it reran the source and installed suites and ten additional adversarial probes. The final report found no further actionable issues within its review scope. These are local results—not GitHub CI, a guarantee of correctness, or production experience.
The connector does not make Jira snapshots atomic. Unknown issues can appear late; access changes can look like deletions; and the first version supports a limited set of attachment formats and unscoped API tokens. The README describes the size, timeout, concurrency, and compatibility limits as well.
Where the work stands
The connector is proposed in PR #462. As of October 31, 2026, it is open, with no review comments or GitHub checks listed. It has been tested locally against synthetic Jira data and has not been deployed to production.
The most useful lesson was to test the uncomfortable transitions: a comment changes quietly, an issue leaves a filter, staging disappears, or the snapshot becomes empty. Those are the moments when stale knowledge can survive. Reviewing those cases repeatedly made the connector—and our evidence for it—more trustworthy.
Top comments (0)