[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M71DYANCKC1CSMHHB9SRJ48R":3,"skill-same:01M71DYANCKC1CSMHHB9SRJ48R":62},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":27,"linkOnly":28,"markdownContent":29,"markdownBody":30,"frontMatter":31,"refName":33,"commitSha":34,"skillDirectory":35,"forksCount":36,"repositoryHtmlUrl":37,"repositoryHomepageUrl":6,"ownerHtmlUrl":38,"ownerBlogUrl":39,"ownerDescription":40,"updatedAt":41,"install":42,"resources":55},"01M71DYANCKC1CSMHHB9SRJ48R","hq-files",null,"Inspect or change HQ vault ACLs, grants, and shared prefixes.","indigoai-us\u002Fhq-core","indigoai-us","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F122242446?v=4","01M71DTQ5NGD10GT1Z7E6XKWC5","HQ","01M71DTQ6BKSYYBNC9RAKT3YDK",94,"MIT","https:\u002F\u002Fgithub.com\u002Findigoai-us\u002Fhq-core\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fhq-files\u002FSKILL.md",[],[19,20,21,22,23,24,25,26],"files","hq files","shared","inspect","Bash","change","grants","vault","2026-11-10 10:12:30",false,"---\nname: hq-files\ndescription: Inspect or change HQ vault ACLs, grants, and shared prefixes.\nallowed-tools: Bash(hq:*)\n---\n\n# HQ Files\n\nManage per-prefix file access controls in the HQ vault via the `hq files` CLI. Access is scoped to S3 key prefixes within a company vault, controlled by an ACL layer that resolves grants at vend-time. See also [`hq-secrets`](..\u002Fhq-secrets\u002FSKILL.md) for secret-level ACLs and group management.\n\n## Requires\n\n- `@indigoai-us\u002Fhq-cli` **≥ 5.142.0** — the first release that preserves the private-folder `foo\u002F` pattern and distinguishes it from the shared glob `foo\u002F*` across direct, browser, ACL, browse, and shared-with-me flows. Older clients rewrite a trailing slash to `\u002F*`; upgrade before using private-folder ACLs. Check with `hq --version`; upgrade via `npm i -g @indigoai-us\u002Fhq-cli@latest`.\n\n## Commands\n\n| Command | Purpose |\n|---------|---------|\n| `hq files share \u003Cprefix>` *(no `--with`)* | **Browser flow.** Mints an encrypted single-use share-session token, opens the share-session page in your default browser, and lets you batch-pick recipients (members, groups, \"Share with All\") with per-recipient read\u002Fwrite before submitting all grants in one click. Supports multiple paths: `hq files share path\u002Fa\u002F path\u002Fb\u002F`. Use `--no-open` to print the URL without launching a browser. |\n| `hq files share \u003Cprefix> --with \u003Cprincipal> --permission \u003Clevel>` | **Direct grant.** Grant `read` or `write` access on a prefix to a person, group, or `@all`. If no ACL row exists for the prefix, one is auto-created with this grant as its first entry; success message is `Created ACL and granted ...` instead of `Granted ...`. |\n| `hq files share \u003Cprefix> --with @all --permission \u003Clevel>` | **Company-wide grant.** Writes a single ACL entry with `granteeType: 'company-wide'` covering every active member. Distinct from the legacy `open` flag (see \"Company-wide vs `open` flag\" below). |\n| `hq files unshare \u003Cprefix> --with \u003Cprincipal>` | Revoke a grant on a prefix from a person, group, or `@all` |\n| `hq files acl \u003Cprefix>` | Show the ACL for a prefix: exact pattern (`foo\u002F` private or `foo\u002F*` shared), creator, grantees, permissions, open\u002Frestricted status, your effective permission |\n| `hq files browse [path]` | **Read, no sync.** List vault objects under `[path]` without materializing them locally. Each row is tagged `shared-with-you`, `creator` (a creator-only child row), `private folder` (the `foo\u002F` marker), or `role-bypass` (owner\u002Fadmin reach only). `--personal` browses your personal vault. |\n| `hq files cat \u003Cpath>` | Stream a single vault object to stdout (or `--out \u003Cfile>`) without syncing it. Refuses `--out` destinations under `\u003ChqRoot>\u002Fcompanies\u002F`. |\n| `hq files search \u003Cquery>` | Case-insensitive path\u002Fname search over a company (or `--personal`) vault listing, no download. `--company \u003Cslug>` required (or `--personal`). |\n| `hq files get \u003Cpath>` | **On-demand materialize.** Download a file\u002Fprefix into local HQ. Default writes in place under `companies\u002F\u003Cslug>\u002F\u003Cpath>` and registers a pin (see \"Pins\") so a scoped sync keeps it; `--into \u003Cdir>` writes elsewhere (no pin). |\n| `hq files shared-with-me [--company]` | List the exact prefixes explicitly shared with you, retaining `foo\u002F` private-folder rows separately from `foo\u002F*` shared globs. Omit `--company` for a cross-company roll-up. Pure read — owner\u002Fadmin role-bypass reach is NOT listed (explicit grants only). |\n\nAll commands accept `--company \u003Cslug>` to target a specific company. If omitted, the CLI resolves the company from your membership.\n\n## Access vs. Download (read without syncing)\n\nVault **access** and local **download** are separate. Your access is decided server-side at vend-time (STS credential scope + ACL resolution + owner\u002Fadmin role-bypass). Download is what a *sync* materializes onto your local HQ tree, governed per-membership by `syncMode` (`all | shared | custom`, set with `hq sync mode`).\n\nThe browse\u002Fretrieve commands let you reach a file you have **access** to without **downloading** your whole share:\n\n- `browse` \u002F `search` \u002F `cat` read straight from the cloud — nothing lands on disk (except `cat --out`, which you choose).\n- `get` materializes exactly the path you ask for, on demand.\n- `hq access \u003Cpath-or-query>` (hq-cli >= 5.109.0) wraps all of this for the \"I can't find or open it\" case: it reports never-existed \u002F not-synced \u002F no-access, runs `get` (and pins) when you have access, and asks the grantor otherwise. Agent sessions reach it as `\u002Fhq-access`.\n\nOwners and admins keep full role-bypass *access* regardless of `syncMode` — narrowing `syncMode` only shrinks what a sync downloads, never what you can browse or get. The engine's scope filter is a footprint\u002FUX optimization, **not** a security boundary: a member who lacks access is still blocked by STS no matter the local filter.\n\nUnder the hood, browse\u002Fcat\u002Fsearch\u002Fget vend through the multi-tenant `\u002Fsts\u002Fvend` (company) and `\u002Fsts\u002Fvend-self` (personal) routes — never the legacy `POST \u002Fvend`. Company vault keys are company-relative; the CLI speaks the anchored `companies\u002F\u003Cslug>\u002F…` form and translates at the S3 boundary.\n\n## Pins (keeping a got file under a scoped sync)\n\n`hq files get` (default in-place mode) records the materialized prefix in a per-machine pin set at `\u003ChqRoot>\u002F.hq\u002Fpins.json`, keyed by company slug. On the next sync, the runner unions a company's pins into its pull scope — so a file you pulled in `shared`\u002F`custom` mode is **not** pruned as an out-of-scope orphan. Pins only ever widen scope (never narrow), and `all` mode ignores them (it pulls everything anyway). `get --into \u003Cdir>` writes outside the sync envelope and registers no pin. The file is plain JSON; delete an entry to let the next scoped sync prune that prefix again.\n\n### Choosing between direct grant and the browser flow\n\n| Situation | Use |\n|-----------|-----|\n| Single recipient, one path, you already know the email\u002Fgroup | `--with` direct grant — one command, no browser hop |\n| 2+ recipients, or 2+ paths, or you want to see who already has access | Browser flow — one share-session, one submit |\n| Granting to the entire company | `--with @all --permission read` (direct) **or** the browser flow's \"Share with All\" toggle |\n| Scripted\u002Fautomated grants | Always direct grant — the browser flow is interactive by design |\n\n## Prefix Conventions\n\nA **prefix** is an S3-style path fragment, **relative to the company's vault bucket root**. The bucket is already scoped to the company — never prepend `companies\u002F\u003Cslug>\u002F`, the company's name, or any other company-identifying segment. A grant on `companies\u002Fmyco\u002Freports\u002F` does not cover `reports\u002F` (those are different keys, and the former does not exist in the bucket).\n\nThe CLI sends the prefix exactly as typed. There are two deliberately different\nfolder patterns:\n\n- **Shared folder glob (`reports\u002Fq3\u002F*`)** — grants ordinary read\u002Fwrite access\n  to every object below that folder.\n- **Private folder (`reports\u002Fq3\u002F`)** — creates a private-by-default folder.\n  `read` lets a grantee see the folder itself, not its children. `write` lets a\n  grantee create one new direct child, but never list, read, overwrite, or\n  delete children. The first authorized upload locks that child to its creator:\n  a file gets a creator-only exact row and a subfolder gets a creator-only\n  `\u002F*` row. Other people need an explicit grant on that child row.\n- **Exact key (`reports\u002Fq3\u002Fsummary.pdf`)** — passed through unchanged.\n\n`reports\u002Fq3\u002F` and `reports\u002Fq3\u002F*` are mutually exclusive at every depth. The\nserver returns `409 ACL_PATTERN_CONFLICT` if either pattern already has its\nsibling row. A **bare prefix without a trailing slash and without `\u002F*`** (for\nexample `reports`) is an exact key; it does not cover anything below it.\n\nThe API rejects prefixes that start with `\u002F` (returns 400). In a shell, quote a\nshared glob (`'reports\u002Fq3\u002F*'`) so pathname expansion does not turn it into a\ndifferent local argument. S3-key character constraints (no `\\0`, `\\n`, traversal\npatterns) are also enforced server-side.\n\nExamples:\n- `reports\u002F` → private folder; new direct children become creator-only\n- `invoices\u002F2025\u002F*` → shared folder glob; grants access to all matching keys\n- `README.md` → grants access to exactly that key\n- `reports` (bare, no slash, no `*`) → grants access to **only** the key literally named `reports` — almost certainly not what you want for a folder\n\n## Permission Model\n\nEach ACL entry grants one of two permission levels via the `--permission` flag.\nFor exact keys and `\u002F*` shared-folder globs:\n\n- **`read`** — caller may list, download, and view metadata for files matching the prefix.\n- **`write`** — full `read` plus upload, overwrite, and delete.\n\nFor a trailing-slash **private folder** (`foo\u002F`), the same words are narrower:\n`read` exposes only `foo\u002F`, and `write` creates one new direct child only. It\ndoes not grant any child read\u002Flist\u002Foverwrite\u002Fdelete authority; child access\nlocks to the first uploader until someone creates an explicit child grant.\n\nThe CLI accepts only `read` or `write` for `--permission`. The ACL row's **creator** additionally gets effective `admin` automatically via creator-bypass at resolution time — visible as `Your effective permission: admin` in `hq files acl` output even when no entry grants admin explicitly. Company **owners and admins** additionally get role-bypass at resolution time — they resolve to `admin` on any prefix, ACL row or not, so `hq files share`-session minting works for them even on prefixes they have no explicit grant on.\n\n**Who can mutate an ACL:**\n\n- **Create an ACL row (`hq files acl \u003Cprefix>` with no existing row)** or **delete one entirely** — owner \u002F admin role only. These are structural operations on the row itself.\n- **Grant a permission within an existing row (`hq files share --with`)** — caller must hold effective permission on the prefix at least as high as the permission being granted (owner\u002Fadmin auto-pass via role bypass; member must have a grant on that row that gives them ≥ the requested level). **Non-bypass roles cannot grant `admin`** — that ceiling is reserved for owner\u002Fadmin role.\n- **Revoke a grant (`hq files unshare`)** — caller must hold effective `write` (or higher) on the prefix. **Revoking an `admin` entry additionally requires owner\u002Fadmin role** (so a member with write cannot nuke admin-level grants on the same row).\n- **Mint a share-session URL (`hq files share \u003Cpaths>`, default flow)** — caller must hold effective permission on every requested path (any non-null level). Owner\u002Fadmin always pass via role bypass; members need an explicit grant. Token is capped at `write` regardless of caller's effective level.\n\nAn ACL may be **open** (all active members get at least `read` automatically) or **restricted** (only explicit entries have access). Most ACLs are created open during the initial backfill; individual grants narrow or extend access on top of the open floor.\n\n**Carve-out Denies:** when a more-specific prefix exists with no entry for a caller, that sub-tree is denied even if a broader prefix grants access. This is enforced at vend-time (STS session policy), not at the ACL API layer.\n\n## Groups as Grantees\n\n`--with` accepts an email address, a group id matching `grp_[A-Za-z0-9_-]+` (letters, digits, underscores, hyphens — e.g. `grp_backend-team`), or the literal `@all` for company-wide grants. A group grant extends the permission to every current member. Adding or removing members from the group adjusts who has file access without touching the ACL.\n\nGroup management is shared with secrets (same `hq groups` subcommands). See [`hq-secrets`](..\u002Fhq-secrets\u002FSKILL.md) for `hq groups create`, `hq groups add`, etc.\n\n## Company-wide vs `open` Flag\n\nTwo ways to give everyone in the company access to a prefix — they look similar but behave very differently:\n\n| | `--with @all` (company-wide entry) | Legacy `open: true` flag |\n|---|---|---|\n| ACL representation | Explicit row, `granteeType: 'company-wide'` | Boolean on the ACL header |\n| Audit trail | Visible in `hq files acl \u003Cprefix>` as a regular grant with grantor + timestamp | Just a flag — no grantor, no timestamp |\n| Revoke | `hq files unshare \u003Cprefix> --with @all` | Requires flipping the flag, often via backfill script |\n| New-member propagation | Automatic at vend-time (every active member resolves through the company-wide entry) | Automatic at vend-time |\n| Distinct from per-member entries | Yes — coexists cleanly with named grants | Conflates \"everyone has read\" with \"this is a public folder\" |\n\n**Always prefer `--with @all` for new company-wide intent.** The `open` flag exists only because most ACLs were created open during the initial backfill — it's load-bearing for legacy data, not the path forward.\n\n## Sharing via the Web Page (Share-Session Flow)\n\nWhen `hq files share` is run with no `--with`, the CLI mints an **encrypted single-use share-session token** and opens a web page where the issuer picks recipients and per-recipient permissions, then submits all grants in one round-trip.\n\nHow the token works:\n\n- **Encrypted at mint time** with the master key (AES-256-GCM, `iv || authTag || ciphertext` base64url-encoded). The Lambda decrypts on every read; the token is opaque to the browser.\n- **Pinned scope** — the encrypted payload includes the issuer's identity, the requested paths, and `maxPermissionByPath` computed from the issuer's own ACL. The page cannot grant beyond what the issuer had at mint time, even if the page is mutated client-side.\n- **Single-use** — the page's submit endpoint claims the token's `nonce` atomically (DynamoDB `attribute_not_exists`). A successful submit invalidates the token; a second submit returns 409.\n- **Short-lived** — default 15-minute TTL, bounded `60s..7d`. The page returns 403 with `expired` if the token is past its `expiresAt`.\n- **Public route, no Cognito session required** — the share-session page lives outside the console's authenticated `(shell)` group at `\u002Fshare-session\u002F[token]`. The token *is* the auth.\n\nFailure modes (operator-visible):\n\n| HTTP | Meaning |\n|------|---------|\n| `403 expired` | Token TTL exceeded |\n| `403 scope_exceeded` | Page tried to grant a permission higher than `maxPermissionByPath[path]` for that path |\n| `409 nonce_already_claimed` | Token was already redeemed — mint a fresh one |\n| `400 invalid_token` | Decryption failed — token corrupted or signed under a different master key (e.g. wrong stage) |\n\n## `unshare` Idempotency\n\n`hq files unshare` is safe to call multiple times. When the grant is already absent, the server returns `404`; the CLI treats this as a successful no-op — it prints a green \"Grant already absent\" message and exits 0. Code or automation calling `unshare` does not need to pre-check whether the grant exists.\n\n## Local Write-Access Enforcement (read-only shares)\n\nVault ACLs are enforced server-side at vend-time, but local HQ sessions also\nmirror them: the PreToolUse hook `enforce-vault-write-access.sh` blocks\nedits, writes, and deletes (Edit\u002FWrite\u002FMultiEdit\u002FNotebookEdit and best-effort\nBash) under `companies\u002F\u003Cslug>\u002F` when the local manifest\n`.hq\u002Fvault-access.json` says your effective permission on that vault path is\nread-only (or absent). This stops doomed local mutations to read-only shares\nbefore the next sync rejects or clobbers them.\n\n- Refresh the manifest after your grants change:\n  `bash core\u002Fscripts\u002Frefresh-vault-access.sh [--company \u003Cslug>]`\n  (sources: `hq files shared-with-me` for grants, `hq members` for your role).\n- Fail-open by design: no manifest, an unlisted company, or role\n  owner\u002Fadmin\u002Funknown means no local enforcement — the server-side STS\u002FACL\n  layer remains the authoritative boundary.\n- Grant matching mirrors the ACL prefix semantics above: `*`, `prefix\u002F*`\n  (covers the bare `prefix` directory too), or an exact key; a private-folder\n  row (`prefix\u002F`) matches the folder marker only and does not authorize a\n  local child write. The most specific matching grant wins, so a specific read\n  grant carves down a broader write grant. The hook cannot prove the server's\n  first-upload creator decision before a child row exists; create-only private\n  writes therefore must go through an authorized server\u002FCLI upload path, or an\n  explicitly approved bypass, until the creator row is visible in the manifest.\n- Bypass requires the user's explicit approval:\n  `\"HQ_BYPASS_VAULT_WRITE_PROTECT\": \"1\"` under `env` in\n  `.claude\u002Fsettings.local.json` — never set it autonomously.\n\n## Rules for Agent Workflows\n\n1. **Choose the folder pattern deliberately before calling `share`.** Pass\n   `reports\u002Fq3\u002F*` when everyone granted the row should access its children.\n   Pass `reports\u002Fq3\u002F` when people should only create a direct child that locks\n   to its first uploader. The CLI preserves both forms; a bare `reports` (no\n   slash, no `*`) is an exact-key grant that covers nothing inside the folder.\n\n2. **Prefixes are bucket-relative — never include the company name or `companies\u002F\u003Cslug>\u002F`.** The vault bucket is already scoped to the company; prepending the slug points the grant at a path that doesn't exist. If the user asks you to \"share `companies\u002Fmyco\u002Freports\u002F` with X\", translate that to `reports\u002F` before calling `hq files share`.\n\n3. **Push local files before sharing them.** `hq files share` only creates an ACL row or share-session token; it does not upload local files. If the requested path exists under `companies\u002F{company}\u002F`, run `hq sync push \u003Clocal-path> --hq-root \u003Chq-root> --company \u003Cslug> --on-conflict keep` first, then verify the plan\u002Fupload count. Treat `0 files to upload` on a newly-created folder as a blocker: the path is probably excluded by `.hqinclude` \u002F `.hqignore`, or the local path does not map to the bucket-relative prefix you plan to grant.\n\n4. **Always confirm the target company.** Run `hq files acl \u003Cprefix> --company \u003Cslug>` to inspect before mutating. A grant on the wrong company uid is hard to clean up.\n\n5. **Verify after sharing.** Immediately after `hq files share`, run `hq files acl \u003Cprefix> --company \u003Cslug>` and confirm the displayed pattern is exactly the one intended: `\u002F*` for a shared folder, `\u002F` for a private create-only folder, or an exact key. If the sibling pattern already exists, do not retry blindly: resolve the `ACL_PATTERN_CONFLICT` by choosing the one intended model. To convert an existing sibling, read its entries, `unshare` every entry (including `@all` where present), verify the old ACL row is absent, then create the new pattern. If other grants must remain, create a replacement shared\u002Fprivate row only after an owner\u002Fadmin deletes the empty conflicting row through the ACL delete route; never leave both siblings in place.\n\n6. **To share everything in a vault, prefer one grant on `*` over many per-folder grants.** Every vault is provisioned with a `*` ACL row; granting the principal `read` on `*` covers all current and future keys. Per-folder fan-out is fragile (easy to miss new top-level folders) and harder to audit.\n\n7. **Prefer group grants for teams.** Share `reports\u002F*` with `grp_finance` rather than granting each person individually. Membership changes automatically adjust access.\n\n8. **Do not widen ACLs without explicit human approval.** Granting `write` on a prefix is a privilege escalation. Always confirm with the human before making these changes.\n\n9. **Check your effective permission before attempting mutation.** `hq files acl \u003Cprefix>` shows `Your effective permission:` in the output. Granting requires that effective level be ≥ the permission you're granting (owner\u002Fadmin always pass via role bypass; non-bypass roles cannot grant `admin`). Revoking requires effective `write` or higher, plus owner\u002Fadmin role for revoking `admin` entries. Creating or deleting the ACL row itself is owner\u002Fadmin-only.\n\n10. **Do not confuse private folders with shared globs.**\n    `reports\u002Fq3\u002Fsummary.pdf` covers one file; `reports\u002Fq3\u002F*` shares every child;\n    `reports\u002Fq3\u002F` lets a writer create one direct child that then becomes\n    creator-only. `reports\u002Fq3\u002F` and `reports\u002Fq3\u002F*` cannot coexist.\n\n11. **Carve-out awareness.** If a broad prefix (`reports\u002F*`) is open and you also need to restrict `reports\u002Fq3\u002F*` for a subset of members, that narrowing is expressed as a more-specific ACL with fewer grants — the vend layer automatically denies the sub-tree for callers without a matching entry. Do not attempt to revoke a broad grant to achieve narrowing; instead, ensure the more-specific prefix has the right entries.\n\n12. **Use the browser flow for 2+ recipients or 2+ paths.** A single share-session page handles N×M grants in one human action — N CLI calls is friction, error-prone, and produces a noisy ACL audit trail. Reserve direct `--with` grants for single-recipient\u002Fsingle-path or scripted automation.\n\n13. **Prefer `@all` over the legacy `open` flag for new company-wide intent.** Explicit `granteeType: 'company-wide'` rows are auditable, individually revocable, and don't conflate \"everyone has read\" with \"this folder is public.\" See \"Company-wide vs `open` Flag\" above.\n\n14. **Treat share-session URLs as live capabilities — render as Markdown at mint, never persist them.** A share-session URL is an encrypted, single-use, 15-minute capability that any holder can redeem to write ACLs in the issuer's name. At the minting turn, surface it **only as a Markdown inline link** — `[Open share-session link — expires \u003Cts> ›](https:\u002F\u002Fhq.{co}.com\u002Fshare-session\u002F\u003Ctoken>)` — never as bare visible text, and never with the token in the visible label (rule: `core\u002Fpolicies\u002Fhq-secure-link-render-as-markdown.md`). Beyond that turn, do **not** paste share-session URLs (bare or as a Markdown href) into:\n    - Auto-checkpoint thread files (`workspace\u002Fthreads\u002F`)\n    - Journal entries, learnings, or session logs\n    - Git commit messages or PR descriptions\n    - Slack, email, or any chat surface other than the intended human recipient\n    - Worker handoff payloads\n    - Any *subsequent* assistant turn that summarizes or revisits the action\n\n    When demonstrating the flow in documentation, redact the token segment as `https:\u002F\u002Fhq.{co}.com\u002Fshare-session\u002F\u003CTOKEN_REDACTED>`. The 15-minute TTL is a defense in depth, not a license to log them.\n\n15. **Mint fresh URLs rather than re-sending stale ones.** If a recipient says \"the link doesn't work,\" mint a new one — do not extend TTLs server-side or attempt to debug an expired token. Mint cost is one round-trip; a stale token can mask scope drift if the issuer's permissions changed since mint.\n\n## Common Workflows\n\n### Share via the browser (multi-recipient or multi-path)\n\n```bash\n# If the folder was created locally, upload it first; sharing only writes ACLs.\nhq sync push companies\u002Fmyco\u002Freports\u002Fq3\u002F --hq-root ~\u002FHQ --company myco --on-conflict keep\n\n# Opens default browser to a share-session page; pick recipients + permissions, click Submit.\n# These explicit globs share the existing trees.\nhq files share 'reports\u002Fq3\u002F*' 'docs\u002Fhandbook\u002F*' --company myco\n\n# Print the URL without launching a browser (useful in headless contexts)\nhq files share 'reports\u002Fq3\u002F*' --no-open --company myco\n# → Share-session URL generated:\n#     https:\u002F\u002Fhq.myco.com\u002Fshare-session\u002F\u003CTOKEN_REDACTED>\n#     Paths:   reports\u002Fq3\u002F*\n#     Expires: 2026-05-12T03:34:16Z\n```\n\n### Share a folder with the entire company (`@all`)\n\n```bash\nhq files share 'announcements\u002F*' --with @all --permission read --company myco\n# → Granted read on announcements\u002F* to @all (granteeType: company-wide)\n#   This is a shared folder: recipients can read every object under announcements\u002F.\n\n# Revoke later — single ACL row, single command\nhq files unshare 'announcements\u002F*' --with @all --company myco\n```\n\n### Share the entire vault with a teammate\n\n```bash\nhq files share '*' --with alice@example.com --permission read --company myco\n# → Granted read on * to alice@example.com\nhq files acl '*' --company myco\n# Confirm alice@example.com appears in Entries with permission=read\n```\n\n### Share a folder with a teammate\n\n```bash\nhq files share reports\u002Fq3\u002F --with alice@example.com --permission read --company myco\n# → Granted read on reports\u002Fq3\u002F to alice@example.com\n\n# Always verify — this is a private folder, not a shared glob\nhq files acl reports\u002Fq3\u002F --company myco\n# ACL for reports\u002Fq3\u002F (private folder, restricted)\n# Children remain creator-only; use reports\u002Fq3\u002F* when the recipient needs them.\n```\n\n### Share a folder with a team group\n\n```bash\nhq files share 'invoices\u002F*' --with grp_finance --permission read --company myco\n```\n\n### Give write access on a subfolder\n\n```bash\nhq files share 'uploads\u002Finbox\u002F*' --with bob@example.com --permission write\n```\n\n### Revoke access\n\n```bash\nhq files unshare reports\u002Fq3\u002F --with alice@example.com --company myco\n# → Removed grant for alice@example.com on 'reports\u002Fq3\u002F'\n```\n\n### Revoke a grant that may or may not exist (idempotent)\n\n```bash\nhq files unshare reports\u002Fq3\u002F --with alice@example.com\n# → Grant already absent for 'reports\u002Fq3\u002F' \u002F alice@example.com   (exits 0)\n```\n\n### Inspect an ACL\n\n```bash\nhq files acl reports\u002Fq3\u002F --company myco\n# ACL for reports\u002Fq3\u002F (private folder, restricted)\n# Creator: person_xxx\n# Your effective permission: read\n# Children are creator-only; use reports\u002Fq3\u002F* for a shared folder.\n# Entries:\n# TYPE   GRANTEE              PERMISSION  GRANTED_BY   GRANTED_AT\n# email  alice@example.com    read        person_xxx   2025-09-01\n# group  grp_finance          read        person_xxx   2025-10-12\n```\n\n### Create a group and share a folder with it\n\n```bash\nhq groups create grp_backend-team --name \"Backend team\"\nhq groups add grp_backend-team alice@example.com\nhq groups add grp_backend-team bob@example.com\nhq files share 'services\u002Flogs\u002F*' --with grp_backend-team --permission read\n```\n\n## Error Reference\n\n| HTTP Status | Meaning |\n|-------------|---------|\n| `400` | Bad request — invalid prefix, invalid permission, or `PolicyNestingUnrepresentable` (3+ nesting depth would produce an unrepresentable IAM policy) |\n| `401` | Not authenticated — run `hq login` |\n| `403 Forbidden: owner or admin role required` | Creating or deleting an ACL row, or you tried to grant `'admin'` as a non-bypass role |\n| `403 Forbidden: caller lacks '\u003Cperm>' on '\u003Cprefix>'` | Granting\u002Frevoking but your effective permission on the prefix is below the requested level (members need a grant ≥ what they're handing out; revoke needs write+) |\n| `403 Forbidden: only owner or admin role may revoke 'admin' entries` | Member with write tried to revoke an entry whose permission is `admin` — that ceiling is reserved for owner\u002Fadmin role |\n| `404` | For `acl`: no ACL record exists yet for this prefix. For `unshare`: grant already absent — the CLI converts this to a no-op and exits 0. `share` no longer surfaces 404 (the row is auto-created on first grant). |\n| `409 ACL_PATTERN_CONFLICT` | A private folder (`foo\u002F`) conflicts with its shared-glob sibling (`foo\u002F*`). Keep the one access model you intend; they cannot coexist. |\n| other `409` | Concurrent modification — retry |\n| any `403` on a read (`browse`, `cat`, `get`, `search`) | You lack a grant on that exact prefix. Every 403 ends with `Run: hq access \u003Cpath>`; run it (or `\u002Fhq-access`) to confirm the file exists, and to ask the grantor for read access with one confirmation. |\n| `5xx` | Server error |\n\nWhen `share` returns `400 PolicyNestingUnrepresentable`, the ACL write is rejected because granting the principal both a broad allow AND a nested allow (with a carve-out Deny in between) would produce an IAM policy exceeding the representability limit. Resolution: grant the principal at the intermediate prefix first (`hq files share \u003Ccarve-out-prefix> --with \u003Cprincipal> --permission read`), then retry the nested grant.\n\n## See also\n\n- `\u002Fhq-access` — a teammate cannot find or open a path: diagnose (never existed \u002F not synced \u002F no access), self-heal, and ask the owner for a grant\n- `\u002Fhq-share` — mint a single-use share link\n- `\u002Fhq-secrets` — same vault, for secrets\n","# HQ Files\n\nManage per-prefix file access controls in the HQ vault via the `hq files` CLI. Access is scoped to S3 key prefixes within a company vault, controlled by an ACL layer that resolves grants at vend-time. See also [`hq-secrets`](..\u002Fhq-secrets\u002FSKILL.md) for secret-level ACLs and group management.\n\n## Requires\n\n- `@indigoai-us\u002Fhq-cli` **≥ 5.142.0** — the first release that preserves the private-folder `foo\u002F` pattern and distinguishes it from the shared glob `foo\u002F*` across direct, browser, ACL, browse, and shared-with-me flows. Older clients rewrite a trailing slash to `\u002F*`; upgrade before using private-folder ACLs. Check with `hq --version`; upgrade via `npm i -g @indigoai-us\u002Fhq-cli@latest`.\n\n## Commands\n\n| Command | Purpose |\n|---------|---------|\n| `hq files share \u003Cprefix>` *(no `--with`)* | **Browser flow.** Mints an encrypted single-use share-session token, opens the share-session page in your default browser, and lets you batch-pick recipients (members, groups, \"Share with All\") with per-recipient read\u002Fwrite before submitting all grants in one click. Supports multiple paths: `hq files share path\u002Fa\u002F path\u002Fb\u002F`. Use `--no-open` to print the URL without launching a browser. |\n| `hq files share \u003Cprefix> --with \u003Cprincipal> --permission \u003Clevel>` | **Direct grant.** Grant `read` or `write` access on a prefix to a person, group, or `@all`. If no ACL row exists for the prefix, one is auto-created with this grant as its first entry; success message is `Created ACL and granted ...` instead of `Granted ...`. |\n| `hq files share \u003Cprefix> --with @all --permission \u003Clevel>` | **Company-wide grant.** Writes a single ACL entry with `granteeType: 'company-wide'` covering every active member. Distinct from the legacy `open` flag (see \"Company-wide vs `open` flag\" below). |\n| `hq files unshare \u003Cprefix> --with \u003Cprincipal>` | Revoke a grant on a prefix from a person, group, or `@all` |\n| `hq files acl \u003Cprefix>` | Show the ACL for a prefix: exact pattern (`foo\u002F` private or `foo\u002F*` shared), creator, grantees, permissions, open\u002Frestricted status, your effective permission |\n| `hq files browse [path]` | **Read, no sync.** List vault objects under `[path]` without materializing them locally. Each row is tagged `shared-with-you`, `creator` (a creator-only child row), `private folder` (the `foo\u002F` marker), or `role-bypass` (owner\u002Fadmin reach only). `--personal` browses your personal vault. |\n| `hq files cat \u003Cpath>` | Stream a single vault object to stdout (or `--out \u003Cfile>`) without syncing it. Refuses `--out` destinations under `\u003ChqRoot>\u002Fcompanies\u002F`. |\n| `hq files search \u003Cquery>` | Case-insensitive path\u002Fname search over a company (or `--personal`) vault listing, no download. `--company \u003Cslug>` required (or `--personal`). |\n| `hq files get \u003Cpath>` | **On-demand materialize.** Download a file\u002Fprefix into local HQ. Default writes in place under `companies\u002F\u003Cslug>\u002F\u003Cpath>` and registers a pin (see \"Pins\") so a scoped sync keeps it; `--into \u003Cdir>` writes elsewhere (no pin). |\n| `hq files shared-with-me [--company]` | List the exact prefixes explicitly shared with you, retaining `foo\u002F` private-folder rows separately from `foo\u002F*` shared globs. Omit `--company` for a cross-company roll-up. Pure read — owner\u002Fadmin role-bypass reach is NOT listed (explicit grants only). |\n\nAll commands accept `--company \u003Cslug>` to target a specific company. If omitted, the CLI resolves the company from your membership.\n\n## Access vs. Download (read without syncing)\n\nVault **access** and local **download** are separate. Your access is decided server-side at vend-time (STS credential scope + ACL resolution + owner\u002Fadmin role-bypass). Download is what a *sync* materializes onto your local HQ tree, governed per-membership by `syncMode` (`all | shared | custom`, set with `hq sync mode`).\n\nThe browse\u002Fretrieve commands let you reach a file you have **access** to without **downloading** your whole share:\n\n- `browse` \u002F `search` \u002F `cat` read straight from the cloud — nothing lands on disk (except `cat --out`, which you choose).\n- `get` materializes exactly the path you ask for, on demand.\n- `hq access \u003Cpath-or-query>` (hq-cli >= 5.109.0) wraps all of this for the \"I can't find or open it\" case: it reports never-existed \u002F not-synced \u002F no-access, runs `get` (and pins) when you have access, and asks the grantor otherwise. Agent sessions reach it as `\u002Fhq-access`.\n\nOwners and admins keep full role-bypass *access* regardless of `syncMode` — narrowing `syncMode` only shrinks what a sync downloads, never what you can browse or get. The engine's scope filter is a footprint\u002FUX optimization, **not** a security boundary: a member who lacks access is still blocked by STS no matter the local filter.\n\nUnder the hood, browse\u002Fcat\u002Fsearch\u002Fget vend through the multi-tenant `\u002Fsts\u002Fvend` (company) and `\u002Fsts\u002Fvend-self` (personal) routes — never the legacy `POST \u002Fvend`. Company vault keys are company-relative; the CLI speaks the anchored `companies\u002F\u003Cslug>\u002F…` form and translates at the S3 boundary.\n\n## Pins (keeping a got file under a scoped sync)\n\n`hq files get` (default in-place mode) records the materialized prefix in a per-machine pin set at `\u003ChqRoot>\u002F.hq\u002Fpins.json`, keyed by company slug. On the next sync, the runner unions a company's pins into its pull scope — so a file you pulled in `shared`\u002F`custom` mode is **not** pruned as an out-of-scope orphan. Pins only ever widen scope (never narrow), and `all` mode ignores them (it pulls everything anyway). `get --into \u003Cdir>` writes outside the sync envelope and registers no pin. The file is plain JSON; delete an entry to let the next scoped sync prune that prefix again.\n\n### Choosing between direct grant and the browser flow\n\n| Situation | Use |\n|-----------|-----|\n| Single recipient, one path, you already know the email\u002Fgroup | `--with` direct grant — one command, no browser hop |\n| 2+ recipients, or 2+ paths, or you want to see who already has access | Browser flow — one share-session, one submit |\n| Granting to the entire company | `--with @all --permission read` (direct) **or** the browser flow's \"Share with All\" toggle |\n| Scripted\u002Fautomated grants | Always direct grant — the browser flow is interactive by design |\n\n## Prefix Conventions\n\nA **prefix** is an S3-style path fragment, **relative to the company's vault bucket root**. The bucket is already scoped to the company — never prepend `companies\u002F\u003Cslug>\u002F`, the company's name, or any other company-identifying segment. A grant on `companies\u002Fmyco\u002Freports\u002F` does not cover `reports\u002F` (those are different keys, and the former does not exist in the bucket).\n\nThe CLI sends the prefix exactly as typed. There are two deliberately different\nfolder patterns:\n\n- **Shared folder glob (`reports\u002Fq3\u002F*`)** — grants ordinary read\u002Fwrite access\n  to every object below that folder.\n- **Private folder (`reports\u002Fq3\u002F`)** — creates a private-by-default folder.\n  `read` lets a grantee see the folder itself, not its children. `write` lets a\n  grantee create one new direct child, but never list, read, overwrite, or\n  delete children. The first authorized upload locks that child to its creator:\n  a file gets a creator-only exact row and a subfolder gets a creator-only\n  `\u002F*` row. Other people need an explicit grant on that child row.\n- **Exact key (`reports\u002Fq3\u002Fsummary.pdf`)** — passed through unchanged.\n\n`reports\u002Fq3\u002F` and `reports\u002Fq3\u002F*` are mutually exclusive at every depth. The\nserver returns `409 ACL_PATTERN_CONFLICT` if either pattern already has its\nsibling row. A **bare prefix without a trailing slash and without `\u002F*`** (for\nexample `reports`) is an exact key; it does not cover anything below it.\n\nThe API rejects prefixes that start with `\u002F` (returns 400). In a shell, quote a\nshared glob (`'reports\u002Fq3\u002F*'`) so pathname expansion does not turn it into a\ndifferent local argument. S3-key character constraints (no `\\0`, `\\n`, traversal\npatterns) are also enforced server-side.\n\nExamples:\n- `reports\u002F` → private folder; new direct children become creator-only\n- `invoices\u002F2025\u002F*` → shared folder glob; grants access to all matching keys\n- `README.md` → grants access to exactly that key\n- `reports` (bare, no slash, no `*`) → grants access to **only** the key literally named `reports` — almost certainly not what you want for a folder\n\n## Permission Model\n\nEach ACL entry grants one of two permission levels via the `--permission` flag.\nFor exact keys and `\u002F*` shared-folder globs:\n\n- **`read`** — caller may list, download, and view metadata for files matching the prefix.\n- **`write`** — full `read` plus upload, overwrite, and delete.\n\nFor a trailing-slash **private folder** (`foo\u002F`), the same words are narrower:\n`read` exposes only `foo\u002F`, and `write` creates one new direct child only. It\ndoes not grant any child read\u002Flist\u002Foverwrite\u002Fdelete authority; child access\nlocks to the first uploader until someone creates an explicit child grant.\n\nThe CLI accepts only `read` or `write` for `--permission`. The ACL row's **creator** additionally gets effective `admin` automatically via creator-bypass at resolution time — visible as `Your effective permission: admin` in `hq files acl` output even when no entry grants admin explicitly. Company **owners and admins** additionally get role-bypass at resolution time — they resolve to `admin` on any prefix, ACL row or not, so `hq files share`-session minting works for them even on prefixes they have no explicit grant on.\n\n**Who can mutate an ACL:**\n\n- **Create an ACL row (`hq files acl \u003Cprefix>` with no existing row)** or **delete one entirely** — owner \u002F admin role only. These are structural operations on the row itself.\n- **Grant a permission within an existing row (`hq files share --with`)** — caller must hold effective permission on the prefix at least as high as the permission being granted (owner\u002Fadmin auto-pass via role bypass; member must have a grant on that row that gives them ≥ the requested level). **Non-bypass roles cannot grant `admin`** — that ceiling is reserved for owner\u002Fadmin role.\n- **Revoke a grant (`hq files unshare`)** — caller must hold effective `write` (or higher) on the prefix. **Revoking an `admin` entry additionally requires owner\u002Fadmin role** (so a member with write cannot nuke admin-level grants on the same row).\n- **Mint a share-session URL (`hq files share \u003Cpaths>`, default flow)** — caller must hold effective permission on every requested path (any non-null level). Owner\u002Fadmin always pass via role bypass; members need an explicit grant. Token is capped at `write` regardless of caller's effective level.\n\nAn ACL may be **open** (all active members get at least `read` automatically) or **restricted** (only explicit entries have access). Most ACLs are created open during the initial backfill; individual grants narrow or extend access on top of the open floor.\n\n**Carve-out Denies:** when a more-specific prefix exists with no entry for a caller, that sub-tree is denied even if a broader prefix grants access. This is enforced at vend-time (STS session policy), not at the ACL API layer.\n\n## Groups as Grantees\n\n`--with` accepts an email address, a group id matching `grp_[A-Za-z0-9_-]+` (letters, digits, underscores, hyphens — e.g. `grp_backend-team`), or the literal `@all` for company-wide grants. A group grant extends the permission to every current member. Adding or removing members from the group adjusts who has file access without touching the ACL.\n\nGroup management is shared with secrets (same `hq groups` subcommands). See [`hq-secrets`](..\u002Fhq-secrets\u002FSKILL.md) for `hq groups create`, `hq groups add`, etc.\n\n## Company-wide vs `open` Flag\n\nTwo ways to give everyone in the company access to a prefix — they look similar but behave very differently:\n\n| | `--with @all` (company-wide entry) | Legacy `open: true` flag |\n|---|---|---|\n| ACL representation | Explicit row, `granteeType: 'company-wide'` | Boolean on the ACL header |\n| Audit trail | Visible in `hq files acl \u003Cprefix>` as a regular grant with grantor + timestamp | Just a flag — no grantor, no timestamp |\n| Revoke | `hq files unshare \u003Cprefix> --with @all` | Requires flipping the flag, often via backfill script |\n| New-member propagation | Automatic at vend-time (every active member resolves through the company-wide entry) | Automatic at vend-time |\n| Distinct from per-member entries | Yes — coexists cleanly with named grants | Conflates \"everyone has read\" with \"this is a public folder\" |\n\n**Always prefer `--with @all` for new company-wide intent.** The `open` flag exists only because most ACLs were created open during the initial backfill — it's load-bearing for legacy data, not the path forward.\n\n## Sharing via the Web Page (Share-Session Flow)\n\nWhen `hq files share` is run with no `--with`, the CLI mints an **encrypted single-use share-session token** and opens a web page where the issuer picks recipients and per-recipient permissions, then submits all grants in one round-trip.\n\nHow the token works:\n\n- **Encrypted at mint time** with the master key (AES-256-GCM, `iv || authTag || ciphertext` base64url-encoded). The Lambda decrypts on every read; the token is opaque to the browser.\n- **Pinned scope** — the encrypted payload includes the issuer's identity, the requested paths, and `maxPermissionByPath` computed from the issuer's own ACL. The page cannot grant beyond what the issuer had at mint time, even if the page is mutated client-side.\n- **Single-use** — the page's submit endpoint claims the token's `nonce` atomically (DynamoDB `attribute_not_exists`). A successful submit invalidates the token; a second submit returns 409.\n- **Short-lived** — default 15-minute TTL, bounded `60s..7d`. The page returns 403 with `expired` if the token is past its `expiresAt`.\n- **Public route, no Cognito session required** — the share-session page lives outside the console's authenticated `(shell)` group at `\u002Fshare-session\u002F[token]`. The token *is* the auth.\n\nFailure modes (operator-visible):\n\n| HTTP | Meaning |\n|------|---------|\n| `403 expired` | Token TTL exceeded |\n| `403 scope_exceeded` | Page tried to grant a permission higher than `maxPermissionByPath[path]` for that path |\n| `409 nonce_already_claimed` | Token was already redeemed — mint a fresh one |\n| `400 invalid_token` | Decryption failed — token corrupted or signed under a different master key (e.g. wrong stage) |\n\n## `unshare` Idempotency\n\n`hq files unshare` is safe to call multiple times. When the grant is already absent, the server returns `404`; the CLI treats this as a successful no-op — it prints a green \"Grant already absent\" message and exits 0. Code or automation calling `unshare` does not need to pre-check whether the grant exists.\n\n## Local Write-Access Enforcement (read-only shares)\n\nVault ACLs are enforced server-side at vend-time, but local HQ sessions also\nmirror them: the PreToolUse hook `enforce-vault-write-access.sh` blocks\nedits, writes, and deletes (Edit\u002FWrite\u002FMultiEdit\u002FNotebookEdit and best-effort\nBash) under `companies\u002F\u003Cslug>\u002F` when the local manifest\n`.hq\u002Fvault-access.json` says your effective permission on that vault path is\nread-only (or absent). This stops doomed local mutations to read-only shares\nbefore the next sync rejects or clobbers them.\n\n- Refresh the manifest after your grants change:\n  `bash core\u002Fscripts\u002Frefresh-vault-access.sh [--company \u003Cslug>]`\n  (sources: `hq files shared-with-me` for grants, `hq members` for your role).\n- Fail-open by design: no manifest, an unlisted company, or role\n  owner\u002Fadmin\u002Funknown means no local enforcement — the server-side STS\u002FACL\n  layer remains the authoritative boundary.\n- Grant matching mirrors the ACL prefix semantics above: `*`, `prefix\u002F*`\n  (covers the bare `prefix` directory too), or an exact key; a private-folder\n  row (`prefix\u002F`) matches the folder marker only and does not authorize a\n  local child write. The most specific matching grant wins, so a specific read\n  grant carves down a broader write grant. The hook cannot prove the server's\n  first-upload creator decision before a child row exists; create-only private\n  writes therefore must go through an authorized server\u002FCLI upload path, or an\n  explicitly approved bypass, until the creator row is visible in the manifest.\n- Bypass requires the user's explicit approval:\n  `\"HQ_BYPASS_VAULT_WRITE_PROTECT\": \"1\"` under `env` in\n  `.claude\u002Fsettings.local.json` — never set it autonomously.\n\n## Rules for Agent Workflows\n\n1. **Choose the folder pattern deliberately before calling `share`.** Pass\n   `reports\u002Fq3\u002F*` when everyone granted the row should access its children.\n   Pass `reports\u002Fq3\u002F` when people should only create a direct child that locks\n   to its first uploader. The CLI preserves both forms; a bare `reports` (no\n   slash, no `*`) is an exact-key grant that covers nothing inside the folder.\n\n2. **Prefixes are bucket-relative — never include the company name or `companies\u002F\u003Cslug>\u002F`.** The vault bucket is already scoped to the company; prepending the slug points the grant at a path that doesn't exist. If the user asks you to \"share `companies\u002Fmyco\u002Freports\u002F` with X\", translate that to `reports\u002F` before calling `hq files share`.\n\n3. **Push local files before sharing them.** `hq files share` only creates an ACL row or share-session token; it does not upload local files. If the requested path exists under `companies\u002F{company}\u002F`, run `hq sync push \u003Clocal-path> --hq-root \u003Chq-root> --company \u003Cslug> --on-conflict keep` first, then verify the plan\u002Fupload count. Treat `0 files to upload` on a newly-created folder as a blocker: the path is probably excluded by `.hqinclude` \u002F `.hqignore`, or the local path does not map to the bucket-relative prefix you plan to grant.\n\n4. **Always confirm the target company.** Run `hq files acl \u003Cprefix> --company \u003Cslug>` to inspect before mutating. A grant on the wrong company uid is hard to clean up.\n\n5. **Verify after sharing.** Immediately after `hq files share`, run `hq files acl \u003Cprefix> --company \u003Cslug>` and confirm the displayed pattern is exactly the one intended: `\u002F*` for a shared folder, `\u002F` for a private create-only folder, or an exact key. If the sibling pattern already exists, do not retry blindly: resolve the `ACL_PATTERN_CONFLICT` by choosing the one intended model. To convert an existing sibling, read its entries, `unshare` every entry (including `@all` where present), verify the old ACL row is absent, then create the new pattern. If other grants must remain, create a replacement shared\u002Fprivate row only after an owner\u002Fadmin deletes the empty conflicting row through the ACL delete route; never leave both siblings in place.\n\n6. **To share everything in a vault, prefer one grant on `*` over many per-folder grants.** Every vault is provisioned with a `*` ACL row; granting the principal `read` on `*` covers all current and future keys. Per-folder fan-out is fragile (easy to miss new top-level folders) and harder to audit.\n\n7. **Prefer group grants for teams.** Share `reports\u002F*` with `grp_finance` rather than granting each person individually. Membership changes automatically adjust access.\n\n8. **Do not widen ACLs without explicit human approval.** Granting `write` on a prefix is a privilege escalation. Always confirm with the human before making these changes.\n\n9. **Check your effective permission before attempting mutation.** `hq files acl \u003Cprefix>` shows `Your effective permission:` in the output. Granting requires that effective level be ≥ the permission you're granting (owner\u002Fadmin always pass via role bypass; non-bypass roles cannot grant `admin`). Revoking requires effective `write` or higher, plus owner\u002Fadmin role for revoking `admin` entries. Creating or deleting the ACL row itself is owner\u002Fadmin-only.\n\n10. **Do not confuse private folders with shared globs.**\n    `reports\u002Fq3\u002Fsummary.pdf` covers one file; `reports\u002Fq3\u002F*` shares every child;\n    `reports\u002Fq3\u002F` lets a writer create one direct child that then becomes\n    creator-only. `reports\u002Fq3\u002F` and `reports\u002Fq3\u002F*` cannot coexist.\n\n11. **Carve-out awareness.** If a broad prefix (`reports\u002F*`) is open and you also need to restrict `reports\u002Fq3\u002F*` for a subset of members, that narrowing is expressed as a more-specific ACL with fewer grants — the vend layer automatically denies the sub-tree for callers without a matching entry. Do not attempt to revoke a broad grant to achieve narrowing; instead, ensure the more-specific prefix has the right entries.\n\n12. **Use the browser flow for 2+ recipients or 2+ paths.** A single share-session page handles N×M grants in one human action — N CLI calls is friction, error-prone, and produces a noisy ACL audit trail. Reserve direct `--with` grants for single-recipient\u002Fsingle-path or scripted automation.\n\n13. **Prefer `@all` over the legacy `open` flag for new company-wide intent.** Explicit `granteeType: 'company-wide'` rows are auditable, individually revocable, and don't conflate \"everyone has read\" with \"this folder is public.\" See \"Company-wide vs `open` Flag\" above.\n\n14. **Treat share-session URLs as live capabilities — render as Markdown at mint, never persist them.** A share-session URL is an encrypted, single-use, 15-minute capability that any holder can redeem to write ACLs in the issuer's name. At the minting turn, surface it **only as a Markdown inline link** — `[Open share-session link — expires \u003Cts> ›](https:\u002F\u002Fhq.{co}.com\u002Fshare-session\u002F\u003Ctoken>)` — never as bare visible text, and never with the token in the visible label (rule: `core\u002Fpolicies\u002Fhq-secure-link-render-as-markdown.md`). Beyond that turn, do **not** paste share-session URLs (bare or as a Markdown href) into:\n    - Auto-checkpoint thread files (`workspace\u002Fthreads\u002F`)\n    - Journal entries, learnings, or session logs\n    - Git commit messages or PR descriptions\n    - Slack, email, or any chat surface other than the intended human recipient\n    - Worker handoff payloads\n    - Any *subsequent* assistant turn that summarizes or revisits the action\n\n    When demonstrating the flow in documentation, redact the token segment as `https:\u002F\u002Fhq.{co}.com\u002Fshare-session\u002F\u003CTOKEN_REDACTED>`. The 15-minute TTL is a defense in depth, not a license to log them.\n\n15. **Mint fresh URLs rather than re-sending stale ones.** If a recipient says \"the link doesn't work,\" mint a new one — do not extend TTLs server-side or attempt to debug an expired token. Mint cost is one round-trip; a stale token can mask scope drift if the issuer's permissions changed since mint.\n\n## Common Workflows\n\n### Share via the browser (multi-recipient or multi-path)\n\n```bash\n# If the folder was created locally, upload it first; sharing only writes ACLs.\nhq sync push companies\u002Fmyco\u002Freports\u002Fq3\u002F --hq-root ~\u002FHQ --company myco --on-conflict keep\n\n# Opens default browser to a share-session page; pick recipients + permissions, click Submit.\n# These explicit globs share the existing trees.\nhq files share 'reports\u002Fq3\u002F*' 'docs\u002Fhandbook\u002F*' --company myco\n\n# Print the URL without launching a browser (useful in headless contexts)\nhq files share 'reports\u002Fq3\u002F*' --no-open --company myco\n# → Share-session URL generated:\n#     https:\u002F\u002Fhq.myco.com\u002Fshare-session\u002F\u003CTOKEN_REDACTED>\n#     Paths:   reports\u002Fq3\u002F*\n#     Expires: 2026-05-12T03:34:16Z\n```\n\n### Share a folder with the entire company (`@all`)\n\n```bash\nhq files share 'announcements\u002F*' --with @all --permission read --company myco\n# → Granted read on announcements\u002F* to @all (granteeType: company-wide)\n#   This is a shared folder: recipients can read every object under announcements\u002F.\n\n# Revoke later — single ACL row, single command\nhq files unshare 'announcements\u002F*' --with @all --company myco\n```\n\n### Share the entire vault with a teammate\n\n```bash\nhq files share '*' --with alice@example.com --permission read --company myco\n# → Granted read on * to alice@example.com\nhq files acl '*' --company myco\n# Confirm alice@example.com appears in Entries with permission=read\n```\n\n### Share a folder with a teammate\n\n```bash\nhq files share reports\u002Fq3\u002F --with alice@example.com --permission read --company myco\n# → Granted read on reports\u002Fq3\u002F to alice@example.com\n\n# Always verify — this is a private folder, not a shared glob\nhq files acl reports\u002Fq3\u002F --company myco\n# ACL for reports\u002Fq3\u002F (private folder, restricted)\n# Children remain creator-only; use reports\u002Fq3\u002F* when the recipient needs them.\n```\n\n### Share a folder with a team group\n\n```bash\nhq files share 'invoices\u002F*' --with grp_finance --permission read --company myco\n```\n\n### Give write access on a subfolder\n\n```bash\nhq files share 'uploads\u002Finbox\u002F*' --with bob@example.com --permission write\n```\n\n### Revoke access\n\n```bash\nhq files unshare reports\u002Fq3\u002F --with alice@example.com --company myco\n# → Removed grant for alice@example.com on 'reports\u002Fq3\u002F'\n```\n\n### Revoke a grant that may or may not exist (idempotent)\n\n```bash\nhq files unshare reports\u002Fq3\u002F --with alice@example.com\n# → Grant already absent for 'reports\u002Fq3\u002F' \u002F alice@example.com   (exits 0)\n```\n\n### Inspect an ACL\n\n```bash\nhq files acl reports\u002Fq3\u002F --company myco\n# ACL for reports\u002Fq3\u002F (private folder, restricted)\n# Creator: person_xxx\n# Your effective permission: read\n# Children are creator-only; use reports\u002Fq3\u002F* for a shared folder.\n# Entries:\n# TYPE   GRANTEE              PERMISSION  GRANTED_BY   GRANTED_AT\n# email  alice@example.com    read        person_xxx   2025-09-01\n# group  grp_finance          read        person_xxx   2025-10-12\n```\n\n### Create a group and share a folder with it\n\n```bash\nhq groups create grp_backend-team --name \"Backend team\"\nhq groups add grp_backend-team alice@example.com\nhq groups add grp_backend-team bob@example.com\nhq files share 'services\u002Flogs\u002F*' --with grp_backend-team --permission read\n```\n\n## Error Reference\n\n| HTTP Status | Meaning |\n|-------------|---------|\n| `400` | Bad request — invalid prefix, invalid permission, or `PolicyNestingUnrepresentable` (3+ nesting depth would produce an unrepresentable IAM policy) |\n| `401` | Not authenticated — run `hq login` |\n| `403 Forbidden: owner or admin role required` | Creating or deleting an ACL row, or you tried to grant `'admin'` as a non-bypass role |\n| `403 Forbidden: caller lacks '\u003Cperm>' on '\u003Cprefix>'` | Granting\u002Frevoking but your effective permission on the prefix is below the requested level (members need a grant ≥ what they're handing out; revoke needs write+) |\n| `403 Forbidden: only owner or admin role may revoke 'admin' entries` | Member with write tried to revoke an entry whose permission is `admin` — that ceiling is reserved for owner\u002Fadmin role |\n| `404` | For `acl`: no ACL record exists yet for this prefix. For `unshare`: grant already absent — the CLI converts this to a no-op and exits 0. `share` no longer surfaces 404 (the row is auto-created on first grant). |\n| `409 ACL_PATTERN_CONFLICT` | A private folder (`foo\u002F`) conflicts with its shared-glob sibling (`foo\u002F*`). Keep the one access model you intend; they cannot coexist. |\n| other `409` | Concurrent modification — retry |\n| any `403` on a read (`browse`, `cat`, `get`, `search`) | You lack a grant on that exact prefix. Every 403 ends with `Run: hq access \u003Cpath>`; run it (or `\u002Fhq-access`) to confirm the file exists, and to ask the grantor for read access with one confirmation. |\n| `5xx` | Server error |\n\nWhen `share` returns `400 PolicyNestingUnrepresentable`, the ACL write is rejected because granting the principal both a broad allow AND a nested allow (with a carve-out Deny in between) would produce an IAM policy exceeding the representability limit. Resolution: grant the principal at the intermediate prefix first (`hq files share \u003Ccarve-out-prefix> --with \u003Cprincipal> --permission read`), then retry the nested grant.\n\n## See also\n\n- `\u002Fhq-access` — a teammate cannot find or open a path: diagnose (never existed \u002F not synced \u002F no access), self-heal, and ask the owner for a grant\n- `\u002Fhq-share` — mint a single-use share link\n- `\u002Fhq-secrets` — same vault, for secrets\n",{"name":5,"description":7,"allowed-tools":32},"Bash(hq:*)","main","205cf61728a27ef15e9153aa38df5856fc8d82f5",".claude\u002Fskills\u002Fhq-files",18,"https:\u002F\u002Fgithub.com\u002Findigoai-us\u002Fhq-core","https:\u002F\u002Fgithub.com\u002Findigoai-us","https:\u002F\u002Fhqforwork.com\u002F","The AI Context Layer for Teams","2026-11-09 23:36:37",{"source":8,"skillName":5,"commands":43,"prompt":53,"zipAvailable":54},[44,47,50],{"tool":45,"command":46},"npx","npx skills add indigoai-us\u002Fhq-core --skill hq-files",{"tool":48,"command":49},"bunx","bunx skills add indigoai-us\u002Fhq-core --skill hq-files",{"tool":51,"command":52},"pnpm","pnpm dlx skills add indigoai-us\u002Fhq-core --skill hq-files","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Findigoai-us\u002Fhq-core\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fhq-files\u002FSKILL.md",true,[56],{"path":57,"resourceType":58,"resourceTypeDesc":59,"sizeBytes":60,"sourceHtmlUrl":61},"agents\u002Fopenai.yaml","REFERENCE","参考文档",117,"https:\u002F\u002Fgithub.com\u002Findigoai-us\u002Fhq-core\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fhq-files\u002Fagents\u002Fopenai.yaml",[63,84,114,138,149,159],{"skillNo":64,"name":65,"nameZh":6,"description":66,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":67,"categories":68,"keywords":69,"publishedAt":27},"01M71E4MQ04WPK1EVDS7VKJ3E8","social-graphic","Generate 1080x1080 HTML social graphics for HQ update posts.","https:\u002F\u002Fgithub.com\u002Findigoai-us\u002Fhq-core\u002Fblob\u002Fmain\u002Fcore\u002Fworkers\u002Fpublic\u002Fsocial-strategist\u002Fskills\u002Fsocial-graphic\u002FSKILL.md",[],[70,71,23,72,73,74,75,76,77,78,79,80,81,82,83],"social","social graphic","html","edit","write","read","update","generate","graphics","browser","open","agent-browser","for","posts",{"skillNo":85,"name":81,"nameZh":6,"description":86,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":87,"categories":88,"keywords":97,"publishedAt":27},"01M71E4H33K4P5H5M7ENPTMFSC","Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to \"open a website\", \"fill out a form\", \"click a button\", \"take a screenshot\", \"scrape data from a page\", \"test this web app\", \"login to a site\", \"automate browser actions\", or any task requiring programmatic web interaction.","https:\u002F\u002Fgithub.com\u002Findigoai-us\u002Fhq-core\u002Fblob\u002Fmain\u002Fcore\u002Fworkers\u002Fpublic\u002Fqa-tester\u002Fskills\u002Fagent-browser\u002FSKILL.md",[89,94],{"categoryNo":90,"categoryName":91,"taxonomyType":92,"taxonomyTypeDesc":93},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",{"categoryNo":95,"categoryName":96,"taxonomyType":92,"taxonomyTypeDesc":93},"01JBSEEDCAT000000000000023","Skill与智能体开发",[98,79,99,100,101,102,23,103,104,105,106,107,80,108,81,109,110,111,112,113],"agent browser","click","ai agents","app","scrape","when","apps","actions","extracting","take a screenshot","testing","programmatic","browser automation","website","cli","interaction",{"skillNo":115,"name":116,"nameZh":6,"description":117,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":118,"categories":119,"keywords":120,"publishedAt":27},"01M71E4FDBD7J8Y45JJ9KMMYGN","work-mesh","Work Mesh Live — automatic presence via hq mesh daemon; context reconcile\u002Forganize; manual session task-status, blocked, and note only.","https:\u002F\u002Fgithub.com\u002Findigoai-us\u002Fhq-core\u002Fblob\u002Fmain\u002Fcore\u002Fskills\u002Fwork-mesh\u002FSKILL.md",[],[121,122,123,23,124,125,126,127,128,129,130,131,132,133,75,134,135,136,137],"work mesh","work","mesh","status","context","note","live","presence","automatic","reconcile","only","organize","task","session","manual","daemon","via",{"skillNo":139,"name":140,"nameZh":6,"description":141,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":142,"categories":143,"keywords":144,"publishedAt":27},"01M71E4EGHNAAGWAPV6KGBMQ9W","review","Parity worker review skill (materialization fixture).","https:\u002F\u002Fgithub.com\u002Findigoai-us\u002Fhq-core\u002Fblob\u002Fmain\u002Fcore\u002Fscripts\u002Ftests\u002Ffixtures\u002Fagent-session\u002Fworker-materialization\u002Fparity-worker\u002Fskills\u002Freview\u002FSKILL.md",[],[140,145,146,147,148],"review skill","fixture","parity","worker",{"skillNo":150,"name":151,"nameZh":6,"description":152,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":153,"categories":154,"keywords":155,"publishedAt":27},"01M71E4D1FZDJPDC15DRERKX8Y","run","Run or list HQ workers (parity fixture).","https:\u002F\u002Fgithub.com\u002Findigoai-us\u002Fhq-core\u002Fblob\u002Fmain\u002Fcore\u002Fscripts\u002Ftests\u002Ffixtures\u002Fagent-session\u002Fskills\u002Frun\u002FSKILL.md",[],[151,156,157,147,158,146],"parity fixture","workers","list",{"skillNo":160,"name":161,"nameZh":6,"description":162,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":163,"categories":164,"keywords":165,"publishedAt":27},"01M71E4BF4MV5SEWZJZ4T1QPQW","handoff","Preserve session state for a follow-up agent (parity fixture).","https:\u002F\u002Fgithub.com\u002Findigoai-us\u002Fhq-core\u002Fblob\u002Fmain\u002Fcore\u002Fscripts\u002Ftests\u002Ffixtures\u002Fagent-session\u002Fskills\u002Fhandoff\u002FSKILL.md",[],[161,156,166,167,134,168,82,147,169,170,146],"up","preserve","session state","follow-up","state"]