> ## Documentation Index
> Fetch the complete documentation index at: https://docs.speckle.systems/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> These docs contain multiple Speckle product experiences. For Speckle 2026.9, prefer /next/ pages for the topics and changes they cover. The navigation label Current does not override this version-specific precedence.
> Coverage under /next/ is incremental. Use Current documentation for unchanged topics and when a /next/ page says coverage is unavailable or explicitly refers you to Current. Do not infer a feature was removed from a missing page.
> Read the page-specific documentation status, applicability, affected guidance and replacement links in agent-only content. Impacted, changed and superseded apply only to the stated scope; they do not mean the entire feature is deprecated. Historical connector guides apply only to the legacy connector described.
> Match guidance to the customer's deployment version, model data format and connector. Respect plan, permission and compatibility restrictions. If an unknown version or data format changes the answer, ask a focused clarification; do not infer deployment version from the date alone.

# Attaching results to objects in 2026.9

> AttachResultToObjects and attach_result_to_objects still key by a content-hashed id. What that means for a function run against a 2026.9-format model.

Documentation set: Speckle 2026.9. Status: current. Authority: canonical for this version and the coverage on this page.

Applies to version 2026.9, from 2026-09; this is not a rollout date for every deployment.

Authoritative Speckle 2026.9 documentation for the topic covered here. For Speckle 2026.9, prefer this page over conflicting earlier guidance. Follow the linked pages in the other documentation version for unchanged topics and where this page says coverage is not yet available. Respect data-format, deployment, plan, permission, and compatibility limitations stated in the page.

<Note>
  This is 2026.9 documentation. Coverage here is incremental: a page exists only where 2026.9
  differs or is newly documented. For any other topic, switch to **Current** (2026.8 and earlier) in
  the version selector.
</Note>

If your function calls `AttachResultToObjects` (.NET) or `attach_result_to_objects` (Python) to flag
individual objects, this page covers what happens when it runs against a model in the 2026.9 data
model. This is a different problem from the results not being visible in the product UI yet — see
[Viewing Automate results in 2026.9](/next/developers/automate/viewing-results) for that. This page
is about the attach call itself, inside your function code.

<Warning>
  There is no dedicated 2026.9 path for this API yet. What's below is the current, accidental
  behavior of the compatibility projection Automate still receives through — not a supported or
  tested contract. Don't build a function that depends on the specifics here holding.
</Warning>

## What the attach call expects

Both SDKs key the objects you pass by `id`, the content-hashed Speckle object id, and carry
`applicationId` alongside it:

```csharp theme={null}
// Speckle.Automate.Sdk — AutomationContext.AttachResultToObjects
Dictionary<string, string?> ids = affectedObjects.ToDictionary(
    x => x.id.NotNull($"You can only attach {level} results to objects with an id"),
    x => x.applicationId
);
```

```python theme={null}
# speckle_automate — AutomationContext.attach_result_to_objects
ids[o.id] = getattr(o, "applicationId", None)
```

Both raise if the object you pass has no `id`. Bundle-only 2026.9 versions have no content-hashed
id at all — see [Object model in 2026.9](/next/developers/object-model/overview) — so this call
should not work against them.

## Why it doesn't fail today

It doesn't fail, but not because it was built to handle 2026.9 data. The objects your function
receives for a bundle-only version still come back as a `Base` tree through Automate's compatibility
projection, and on that projection `id` is set equal to `applicationId` rather than left as a
content hash. The dictionary above ends up keying an object by its own applicationId
(`ids[applicationId] = applicationId`), which happens to satisfy the `NotNull` check and produce a
result Automate can store.

That's a side effect of a decision made for a different problem (receive fidelity on the
compatibility path), not a designed identity story for Automate. There is no committed spec for
what this API looks like once Automate has its own bundle-era path, and no ticket number to point at
yet.

<Warning>
  An object your function constructs itself, or receives with neither `id` nor `applicationId` set,
  still throws. This isn't limited to objects you read from the model — check any object you build
  in the function before attaching a result to it.
</Warning>

## Results stored before a model was migrated

A run that finished before its model moved to the 2026.9 format stored its results against the
content-hashed `id`. Migration doesn't carry that hash into the migrated data: objects are
identified by `applicationId` only, and an object that had no `applicationId` is keyed as
`spk:` followed by its old hash. The stored results are kept as they were, but the ids inside
them no longer match any object in the migrated model.

Runs made after migration don't have this problem, because they key results by `applicationId`.
Re-run the automation against the migrated version to get results that line up with its objects.

## What to do today

<Steps>
  <Step title="Make sure every object you attach results to has an applicationId">
    Objects you receive from a 2026.9-format model carry one. Objects your function constructs
    itself need `applicationId` set explicitly, or the attach call throws regardless of `id`.
  </Step>

  <Step title="Don't branch your function on id being a content hash">
    Don't parse, compare, or cache by `id` expecting it to be a stable hash of the object's content.
    For a bundle-only version it's the same value as `applicationId`; for an object-graph version
    it's still a real hash. Treat `id` as an opaque key either way.
  </Step>

  <Step title="Expect this to change without a migration path">
    Because this is compatibility-projection behavior and not a designed contract, a future Automate
    release can change how identity is keyed here without warning. Don't build tooling that depends
    on `id == applicationId` continuing to hold.
  </Step>
</Steps>

## FAQ

<AccordionGroup>
  <Accordion title="Is this the same issue as the missing results icon?">
    No. [Viewing Automate results in 2026.9](/next/developers/automate/viewing-results) is about the
    product UI not showing results yet. This page is about the attach call inside your function
    succeeding or failing. A function can attach results successfully and still have nothing visible
    in the viewer.
  </Accordion>

  <Accordion title="Will AttachResultToObjects get a dedicated 2026.9 signature?">
    Automate's bundle-era story is a separate, not-yet-scoped effort. There's no spec, ADR, or
    ticket for it yet. This page will be replaced once one exists.
  </Accordion>

  <Accordion title="Do my old results still point at the right objects after migration?">
    Not by id. Results stored before migration reference the old content-hashed `id`, which the
    migrated model no longer carries. Re-run the automation on the migrated version.
  </Accordion>

  <Accordion title="Should I catch the NotNull exception defensively?">
    Better to make sure every object you attach a result to carries an `applicationId` before you
    call the attach method. Catching the exception hides a real problem: an object your function
    built without identity set.
  </Accordion>
</AccordionGroup>
