hcengineering/platform · error

Association not found: ${key}, ${cardPath}

Error message

Association not found: ${key}, ${cardPath}

What it means

Same defensive pattern as the attribute check, but for associations: the key matched masterTagAssociaions or tagAssociations via has(), yet get() returned undefined. In practice it indicates concurrent map mutation or an inconsistent Map-like implementation, since the has-check just succeeded.

Source

Thrown at packages/importer/src/huly/cards.ts:583

        const attrType = attrProps.type
        const attrBaseType = attrType._class === core.class.ArrOf ? attrType.of : attrType
        const values = attrType._class === core.class.ArrOf ? value : [value]
        const propValues = []
        for (const val of values) {
          if (attrBaseType._class === core.class.RefTo) {
            const refPath = path.resolve(path.dirname(cardPath), val)
            const ref = this.metadataRegistry.getRef(refPath) as Ref<Card>
            propValues.push(ref)
          } else {
            propValues.push(val)
          }
        }
        cardProps[attrProps.name] = attrType._class === core.class.ArrOf ? propValues : propValues[0]
      } else if (masterTagAssociaions.has(key) || tagAssociations.has(key)) {
        const metadata = masterTagAssociaions.get(key) ?? tagAssociations.get(key)
        if (metadata === undefined) {
          throw new Error(`Association not found: ${key}, ${cardPath}`)
        }
        const values = Array.isArray(value) ? value : [value]
        for (const val of values) {
          const otherCardPath = path.resolve(path.dirname(cardPath), val)
          const otherCardId = this.metadataRegistry.getRef(otherCardPath) as Ref<Card>
          const relation: UnifiedDoc<Relation> = this.createRelation(metadata, cardId, otherCardId)
          relations.push(relation)
        }
      }
    }

    return [
      {
        _class: masterTagId,
        collabField: 'content',
        contentProvider: () => Promise.resolve(this.parser.readMarkdownContent(cardPath)),
        props: cardProps as Props<Card>
      },

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Snapshot both association maps before the card-processing loop to isolate from concurrent rebuilds.
  2. Serialize processing stages so associations are fully built before cards referencing them are processed.
  3. Replace any Map-like wrapper with a real Map.

Example fix

// before
const metadata = masterTagAssociaions.get(key) ?? tagAssociations.get(key)
// after — guard with explicit lookup
const metadata = masterTagAssociaions.get(key) ?? tagAssociations.get(key)
if (metadata === undefined) throw new Error(`Association not found: ${key}, ${cardPath}`)
Defensive patterns

Strategy: try-catch

Validate before calling

function associationsReady(a: Map<string, unknown>, t: Map<string, unknown>, keys: string[]): string[] {
  return keys.filter(k => !a.has(k) && !t.has(k))
}

Type guard

function isRealMap<K, V>(m: unknown): m is Map<K, V> {
  return m instanceof Map
}

Try / catch

try {
  await processor.cardWithRelations(cardPath)
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Association not found:')) {
    console.error('Association maps were mutated or built after cards were processed — build associations first, then snapshot')
  } else throw e
}

Prevention

When it happens

Trigger: masterTagAssociaions/tagAssociations rebuilt or cleared while createCardWithRelations is executing (e.g. parallel processing of directories sharing the maps); passing proxy/wrapper objects where has() and get() disagree.

Common situations: Multi-threaded or interleaved async import pipelines sharing processor state; custom caching layer around the association maps.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/8dc40c08cd4b2076. Report an issue: GitHub.