canonical / canonical/operator

`relation-get`/`relation-list` on a gone relation raise an opaque `ModelError("permission denied")` instead of `RelationNotFoundError`

Open
#2,709 6 comments 0 reactions 0 assignees View on GitHub
next release rainy day small item
Dominant language
Python
Stars
267
Forks
136
Avg merge
1d 22h
Merged PRs (30d)
28

Description

Hi mates!

I'm troubleshooting [this error in opentelemetry-collector-k8s](https://github.com/canonical/opentelemetry-collector-k8s-operator/issues/355), and as far as I understand there is room to improve the way ops works with missing remote relations.

Juju's uniter facade returns `permission denied` (not `relation not found`) for hook commands on a relation that no longer exists in state (cross-model relation in teardown, `remove-saas`, `remove-relation --force`, app removed with `--force`).

ops maps only the "relation not found" flavor to `RelationNotFoundError` ([`_wrap_hookcmd`, `ops/model.py#L3596`](https://github.com/canonical/operator/blob/d9d03ab5f987cfe52270c06266e2249d4ca41b8f/ops/model.py#L3596)), which is handled gracefully:

- [`Relation.__init__` catches it → `active = False`](https://github.com/canonical/operator/blob/d9d03ab5f987cfe52270c06266e2249d4ca41b8f/ops/model.py#L1715-L1717) (dead relation, no units);
- [`RelationDataContent._load` catches it → returns `{}`](https://github.com/canonical/operator/blob/d9d03ab5f987cfe52270c06266e2249d4ca41b8f/ops/model.py#L2001-L2012) ("dead relations tell no tales").

The "permission denied" flavor surfaces as a plain `ModelError`, which:

- crashes `Relation.__init__` ([the `relation_list` call at `ops/model.py#L1709`](https://github.com/canonical/operator/blob/d9d03ab5f987cfe52270c06266e2249d4ca41b8f/ops/model.py#L1709)), failing the whole endpoint's relation list construction ([`RelationMapping.__getitem__`](https://github.com/canonical/operator/blob/d9d03ab5f987cfe52270c06266e2249d4ca41b8f/ops/model.py#L941)), and
- crashes remote application databag reads ([`RelationDataContent._load` → `relation_get`](https://github.com/canonical/operator/blob/d9d03ab5f987cfe52270c06266e2249d4ca41b8f/ops/model.py#L2001-L2012)).

## References

- canonical/operator#2575: better context for ModelError
- canonical/operator#745: dangling relations produce inconsistent errors
- Juju facade: [`getRemoteRelationAppSettings` returns `ErrPerm` for missing relations](https://github.com/juju/juju/blob/8f61c6be244b734bf669819351ff7601d0425876/apiserver/facades/agent/uniter/uniter.go#L1802)
- canonical/opentelemetry-collector-k8s-operator#355: production crash that motivated this

Contributor guide

Open the contributing guide

Research direction

Start in ops/model.py at _wrap_hookcmd, Relation.__init__, and RelationDataContent._load, then trace the relation_list and relation_get calls described in the issue. Check how the existing RelationNotFoundError handling behaves for gone relations. Done means both permission-denied cases follow the same graceful dead-relation behavior instead of surfacing as a plain ModelError.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.