Skip to main content
← Back to list
01Issue
BugClosedSwamp CLIPublic
Assigneesstack72

Relationships

#1808 CEL assert regression after 20260824 update: @swamp/ssh runResult .attributes access now errors

Opened by taylorparsons · 8/24/2026

Description

After running swamp update (binary moved from 20260819.011806.0 to 20260824.203759.0), a workflow assert step using the CEL pattern data.latest("<model>", "<dataName>").attributes.<field> against an @swamp/ssh runResult resource started failing with:

Invalid expression: No such key: attributes

The workflow (claire-cross-deploy) had run successfully with this exact pattern before the update. The runResult resource spec's own JSON schema (swamp model type describe @swamp/ssh --json) never had an attributes wrapper -- exitCode, stdout, stderr, method, host, durationMs, etc. are all top-level fields on the resource object, and the stored data confirms this (swamp data get <model> <dataName> --json shows content.exitCode etc. top-level, no attributes key). So .attributes.exitCode was arguably always semantically wrong for this model type -- but it worked (silently tolerated, or resolved some other way) on the 20260819 binary and hard-errors on the 20260824 binary.

Steps to reproduce

  1. Author a workflow with an @swamp/ssh exec step producing a runResult data output.
  2. Add an assert step: expr: data.latest("<model>", "runResult").attributes.exitCode == 0
  3. On swamp 20260819.011806.0: assert passes.
  4. swamp update to 20260824.203759.0, re-run the same workflow with no other changes: assert now fails with Invalid expression: No such key: attributes.

Environment

  • swamp version: upgraded 20260819.011806.0 -> 20260824.203759.0 (bug appears only on the newer version)
  • OS: macOS (Darwin 25.5.0)
  • Model type: @swamp/ssh, method exec, resource runResult

Additional notes

Notably, the same repo has a second assert against a DIFFERENT model type's data output using the identical .attributes.<field> pattern -- data.latest("<name>", "result").attributes.exitCode == 0 against a command/shell model's result resource -- and that one is STILL PASSING live on the 20260824 binary, even though command/shell's result schema also appears to put fields top-level rather than under attributes (not independently confirmed via model type describe for this report, but the assert never errored). So the schema-enforcement tightening (if that's the mechanism) seems to be inconsistent across model types, not a blanket change -- which suggests either the CEL resolver's handling of unknown-key access differs per resource schema shape, or something more specific to @swamp/ssh's runResult schema changed on 20260824. Worked around in our workflow by removing .attributes from every @swamp/ssh runResult reference; flagging because the underlying trigger (something about the 20260824 update tightened/changed CEL schema-key enforcement in a way that broke a previously-passing workflow with no workflow-side change) seems worth swamp's own investigation, and because a sibling bug in this same workflow file (a guard/boolean CEL issue, found earlier in the same feature) was not filed as a defect report when it should have been -- filing this one to not repeat that.

02Bog Flow
OPENTRIAGEDIN PROGRESSCLOSED+ 1 MOREASSIGNEDCLASSIFICATION

Closed

8/25/2026, 2:52:57 AM

No activity in this phase yet.

03Sludge Pulse
stack72 assigned stack728/24/2026, 11:21:39 PM
Editable. Press Enter to edit.

taylorparsons commented 8/24/2026, 11:19:10 PM

Correction to this report's diagnosis (filed same-day, same workflow, T-1672).

Re-running claire-cross-deploy live after removing .attributes (the workaround described above) proved the original diagnosis wrong: the assert now fails with Invalid expression: No such key: exitCode instead of the original No such key: attributes. .attributes.<field> was and is the correct accessor for a data.latest() result -- this repo's own swamp skill reference doc (references/workflow/reference.md lines 807-811) says so explicitly, and it did not change on 20260824.

Root cause, confirmed via swamp data query / swamp data list: the household-fleet model in this repo has accumulated data from two distinct hosts it manages -- run-exec-thinkpad (this workflow's own steps) and run-exec-clara (from separate testing sessions against the same model). Both data NAMES carry the same specName: "runResult" TAG. data.latest(modelName, specName) resolves by that tag, and with two data names sharing one specName tag, the match is ambiguous. That ambiguity -- not a schema/attributes change -- is what changed behavior between the 20260819 and 20260824 binaries: something in the resolution/tie-breaking for an ambiguous specName match evidently changed (silently resolving to something with an attributes key before, and to something without one now), rather than .attributes access itself being newly rejected.

So there may still be a real swamp defect here, but it's data.latest()'s ergonomics/behavior under an ambiguous specName match across data names sharing a model, not .attributes handling for @swamp/ssh's runResult schema. Worth swamp's own investigation: (a) should data.latest(model, specName) error loudly on an ambiguous specName-tag match across multiple data names instead of silently picking one, and (b) what specifically changed in that resolution between 20260819.011806.0 and 20260824.203759.0.

Our workaround is corrected on our side: every affected assert now names the specific, unambiguous data name (data.latest("household-fleet", "run-exec-thinkpad").attributes.<field>) instead of the ambiguous specName tag ("runResult"), with .attributes restored. Full writeup: docs/specs/20260824-claire-cross-build-automation/notes.md (session note appended under T-1672).

stack72 commented 8/25/2026, 12:03:45 AM

@taylorparsons Thanks for the detailed report — the binary version pinpointing and the command/shell comparison are really helpful.

I need a couple of details from your workflow to pin down the exact trigger:

  1. Can you share the relevant snippet of the claire-cross-deploy workflow YAML? Specifically the @swamp/ssh exec step and the assert step that fails — I need to see the exact expr value, any dependsOn, and whether the SSH step uses forEach.

  2. What was the exact CEL expression? The issue says data.latest("<model>", "<dataName>").attributes.<field> — what were the actual model name and data name arguments? (e.g., was the data name runResult, or a per-host instance name like run-exec-hostname?)

  3. When you say the workaround was "removing .attributes" — did you change the expression from .attributes.exitCode to just .exitCode directly on the data.latest() result, and that worked? Or did you change the access pattern more broadly (e.g., switching to data.findBySpec or content.exitCode)?

  4. Was the assert step's status showing as "passed" on the 20260819 binary, or was it "skipped"? A fix also landed in this window that changes how steps depending on forEach steps are evaluated — if the assert was being skipped before and only started running on the new binary, that changes the diagnosis.

stack72 commented 8/25/2026, 2:52:57 AM

@taylorparsons The root cause here is the is_latest cross-demotion regression fixed in #1802 (PR #2247, commit 59b73704). Your household-fleet model has data from workflow step writes (run-exec-thinkpad) and standalone model-method writes (run-exec-clara) — the is_latest scoping change in the 20260824 binary broke demotion between those two write contexts, causing data.latest() to resolve to the wrong row.

This fix is already merged to main. Run swamp update to pick it up, then re-run claire-cross-deploy with your original .attributes expressions and confirm the assert passes.

Separately — your point about data.latest() silently picking a winner when multiple data names share a specName tag is a fair one. Filing that as a separate enhancement for ambiguous-match detection.

Sign in to post a ripple.